I discovered a technique to await on events and in this post I’ll show it and describe how it works. It’s needed only in rare cases, but the technique is interesting enough to write a post about it.

The app

I made a small app to only demonstrate the technique. Normally you should be using MVVM commands for the buttons together with a view model for the view. The technique is useful only when you need to do acrobatics with events - meaning more complicated things.

I tried to simulate a bit more complex event behavior with this app which waits until the user clicks on both buttons and then moves the mouse over the blue label - after that the app will show a message in the TextBlock.

Here’s how the app looks like:

Awaiting on Events

The technique

public static class AwaitableEventExtensions
{
    public static async Task ClickAsync(this Button button)
    {
        var source = new TaskCompletionSource<object>();
        RoutedEventHandler handler = (sender, args) =>
        {
            source.TrySetResult("ignored");
        };

        try
        {
            button.Click += handler;
            await source.Task;
        }
        finally
        {
            button.Click -= handler;
        }
    }
}

How to use it:

private async void Window_Loaded(object sender, RoutedEventArgs e)
{
    var firstButtonTask = FirstButton.ClickAsync();
    var secondButtonTask = SecondButton.ClickAsync();
    var labelTask = TheLabel.MouseEnterAsync();

    await Task.WhenAll(firstButtonTask, secondButtonTask, labelTask);

    TheTextBlock.Text = "Clicked on both buttons and mouse moved over the label";
}

How it works

To understand how it works first we need to know what TaskCompletionSource does. Simply explained it creates a task that is not completed, but when you call TrySetResult it will complete.

We subscribe to the button.Click event so when it is triggered we will mark the task as complete. Next, we await for the source.Task to complete. This will suspend the method execution until the task is completed which happens only when the user clicks the button. When that happens, the method will resume execution after await source.Task and the finally block will do some clean up like unsubscribing from the event.

In this example we just wait until all events are completed and then show a message.

Source Code

The full source code is available on GitHub. You can clone the repo and try it locally.

Resources

You can read my previous posts about asynchronous programming to find out more about async. To learn more about this specific technique I recommend this video: Microsoft Channel9: Wrap events up in Task-returning APIs and await them