День двести пятнадцатый. #ЗаметкиНаПолях

Многопоточность.

9. Async/await. Продолжение

Паттерн awaitable

Паттерн awaitable используется для определения типов, которые можно использовать с оператором await. Можно было бы ожидать интерфейса вроде IDisposable для оператора using. Однако поддержка await основана на шаблоне. Допустим, есть выражение типа T, которое необходимо ожидать. Компилятор выполняет следующие проверки:

1. T должен иметь метод GetAwaiter() без параметров, либо должен существовать метод расширения, принимающий один параметр типа T. Метод GetAwaiter не должен быть пустым. Тип возвращаемого значения метода называется awaiter («ожидатель»).

2. Awaiter должен реализовывать System.Runtime.INotifyCompletion, имеющий единственный метод: void OnCompleted(Action).

3. Awaiter должен иметь свойство IsCompleted типа bool.

4. Awaiter должен иметь метод GetResult() без параметров.

5. Этим не обязательно быть открытыми, но они должны быть доступны из асинхронного метода, в котором используется await.

6. Тип результата выражения await определяется типом результата метода GetResult. Если это тип void, значит await не возвращает значения.



Рассмотрим статический метод Task.Yield(). В отличие от большинства других методов класса Task, метод Yield() возвращает не задачу, структуру YieldAwaitable. Вот упрощенная версия задействованных типов:

public class Task

{



public static YieldAwaitable Yield();

}

public struct YieldAwaitable

{

public YieldAwaiter GetAwaiter();



public struct YieldAwaiter : INotifyCompletion

{

public bool IsCompleted { get; }

public void OnCompleted(Action continuation);

public void GetResult();

}

}

YieldAwaitable следует паттерну awaitable, описанному ранее. Поэтому можно вызвать:

await Task.Yield();

GetResult структуры YieldAwaiter возвращает void, поэтому код выше не имеет результата. То есть следующий код недопустим:

var result = await Task.Yield();



Продолжение следует…



Источник: Jon Skeet “C# In Depth”. 4th ed – Manning Publications Co, 2019. Глава 5.