Почти 100% асинхронных задач, с которыми вы сталкиваетесь в своем коде C #, выполняются или уже выполнены, независимо от того, «ожидаете» вы их или нет. Это можно показать на следующем примере:
static async Task Main() { var task = GetValueAsync(); await Task.Delay(500); Console.WriteLine("Awating..."); Console.WriteLine("Result is: " + await task); } static async Task<int> GetValueAsync() { Console.WriteLine("Async Start"); await Task.Delay(100); Console.WriteLine("Async End"); return 42; }
Вывод:
Async Start Async End Awating... Result is: 42
Как вы видите, GetValueAsync завершился до await, но что, если поведение нежелательно и вы не хотите, чтобы задача начинала выполняться, пока не будет хотя бы один «ожидающий»? Конечно, вы можете использовать Lazy - общий класс из пространства имен System:
static async Task Main() { var task = new Lazy<Task<int>>(GetValueAsync); await Task.Delay(500); Console.WriteLine("Awating..."); Console.WriteLine("Result is: " + await task.Value); } static async Task<int> GetValueAsync() { Console.WriteLine("Async Start"); await Task.Delay(100); Console.WriteLine("Async End"); return 42; }
Вывод:
Awating... Async Start Async End Result is: 42
Однако есть другое решение, которое незначительно повлияет на ваш клиентский код - это замена Task, возвращаемого вашей асинхронной функцией, на LazyTask - класс, который вы можете создать своим использовать новую функцию C # 7 - Обобщенные асинхронные возвращаемые типы
В результате ваш код будет выглядеть следующим образом:
static async Task Main() { var task = GetValueAsync(); await Task.Delay(500); Console.WriteLine("Awating..."); Console.WriteLine("Result is: " + await task); } static async LazyTask<int> GetValueAsync() { Console.WriteLine("Async Start"); await Task.Delay(100); Console.WriteLine("Async End"); return 42; }
Вывод:
Awating... Async Start Async End Result is: 42
Как вы знаете, компилятор C # преобразует методы, помеченные как async, в конечный автомат, где каждое состояние представляет собой завершение асинхронной операции с пометкой await. Обобщенные асинхронные возвращаемые типы позволяют получить доступ к выполнению конечного автомата (я подробно изучал этот механизм в одной из своих предыдущих статей.), а в нашем случае нам просто нужно отложить первый шаг пока у нас не будет хотя бы одного ожидающего. Это можно сделать, настроив метод Старт:
public class LazyTaskMethodBuilder<T> { public LazyTaskMethodBuilder() => this.Task = new LazyTask<T>(); public void Start<TStateMachine>( ref TStateMachine stateMachine).. { //instead of stateMachine.MoveNext(); this.Task.SetStateMachine(stateMachine); } ... }
Вместо запуска первого шага метод Start просто сохраняет ссылку на конечный автомат внутри объекта LazyTask.
Теперь первый шаг можно вызвать только тогда, когда у нас есть первый «ожидающий»:
public void OnCompleted(Action continuation) { ... this._asyncStateMachine.MoveNext(); ... }
Примечание. Интересно, что этот трюк работает даже с методами, не имеющими внутри асинхронных вызовов (без ключевого слова «await»).
Это все!
В заключение я хочу сказать, что Обобщенные асинхронные возвращаемые типы - мощный механизм, а LazyTask - лишь один из примеров того, как его можно использовать.
Ссылки
2. «Может быть монада через async / await в C # (No Tasks!) »- статья, в которой подробно рассматриваются обобщенные типы возврата async.