Превращаем асинхронные события в async/await

Пока еще в .NET существуют классы (особенно от сторонних разработчиков), которые используют старую модель для асинхронности. Речь идет реализации с помощью событий. Однако, преобразовать такой код к виду async/await очень легко.

Поможет в этом TaskCompletionSource. Для примера возьмем некий класс Controller. Он содержит метод BeginRead() для начала операции асинхронного чтения данных. После её завершения пользователь может получить уведомление с помощью события Completed.

Стандартный для подобного подхода код выглядит примерно так:

var controller = new Controller();
controller.Completed += (s, e) => { /* обработка прочитанных данных */ };
controller.BeginRead();

Его можно переписать в стиле async/await c помощью своего метода-расширения:

public static async Task ReadAsync(this Controller controller)
{
    var tcs = new TaskCompletionSource<object>();
    EventHandler handler = (s, e) => tcs.TrySetResult(null);

    try {
        controller.Completed += handler;
        controller.BeginRead();
        await tcs.Task;
    }
    finally {
        controller.Completed -= handler;
    }
}

И теперь в коде приложения можно использовать более понятную запись:

await controller.ReadAsync();
/* обработка прочитанных данных */

Кстати, подобные методы-расширения можно создавать и для простого ожидания событий. Например:

public static async Task WaitForClick(this Button button)
{
    var tcs = new TaskCompletionSource<object>();
    EventHandler handler = (s, e) => tcs.TrySetResult(null);

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

Это позволит ожидать нажатия кнопки следующим вызовом:

await button.WaitForClick();