В эпоху повсеместного использования многоядерных процессоров, асинхронного программирования и очень пристальному вниманию пользователей к отзывчивости интерфейсов очень важной характеристикой языка программирования является удобство пользования инструментарием для реализации асинхронности.
Я бы хотел рассказать о такой удобной вещи языка C# как написание своих awaitable типов на примере одного простой но забаной утилиты.
Позвольте кратко напомнить суть async и await.
В .NET 4.0 появился TLP и вместе с ним Task/Task которые представляли собой удобную абстракцию для некоторой CPU или IO bound задачи имеющей или не имеющей результат, а также которая может провалиться выбросив исключение, либо быть отмененной до или во время своего выполнения.
Здесь важно понимать что абстракция по своей природе никак явно не связана с потоками, хотя именно c исполнением операции на отдельном потоке как правило Task и ассоциируется.
Я бы хотел рассказать о такой удобной вещи языка C# как написание своих awaitable типов на примере одного простой но забаной утилиты.
Позвольте кратко напомнить суть async и await.
В .NET 4.0 появился TLP и вместе с ним Task/Task которые представляли собой удобную абстракцию для некоторой CPU или IO bound задачи имеющей или не имеющей результат, а также которая может провалиться выбросив исключение, либо быть отмененной до или во время своего выполнения.
Здесь важно понимать что абстракция по своей природе никак явно не связана с потоками, хотя именно c исполнением операции на отдельном потоке как правило Task и ассоциируется.
Пара примеров использования
Здесь Task.Run запустит исполнение метода CalculatePi на пуле потоков
А тут мы получили Task в состоянии завершенной задачи прямо из литерала
А вот здесь мы контролируем Task вручную, с помощью управляющего TaskCompletionSource
Все это прекрасно, но работа с Task с точки зрения потребителя — не слишком то удобна.
Получили мы Task, но чтобы дождаться его результата нам необходимы Continuation'ы.
В итоге код
Task<double> bgTask = Task.Run(() => CalculatePi());
А тут мы получили Task в состоянии завершенной задачи прямо из литерала
Task<double> completedTask = Task.FromResult(3.14d);
А вот здесь мы контролируем Task вручную, с помощью управляющего TaskCompletionSource
TaskCompletionSource<double> taskSource = new TaskCompletionSource<double>();
GlobalPiCalculator.PiCalculated += (pi) => taskSource.SetResult(pi);
GlobalPiCalculator.DoYourJob();
Task<double> manualPiTask = taskSource.Task;
Все это прекрасно, но работа с Task с точки зрения потребителя — не слишком то удобна.
Получили мы Task, но чтобы дождаться его результата нам необходимы Continuation'ы.
В итоге код
Поделиться с друзьями