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

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

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

Выражения await

Синтаксис выражения await прост: за оператором await следует другое выражение, которое возвращает значение. Вы можете ожидать результата вызова метода, переменной или свойства. Также выражение не обязано быть простым: можно объединить вызовы методов и дождаться результата:

int result = await foo.Bar().Baz();

Приоритет оператора await ниже, чем у точки, поэтому этот код эквивалентен следующему:

int result = await (foo.Bar().Baz());



Ограничения выражений await

На выражения await накладываются некоторые ограничения.

1. Во-первых, выражения должны быть «ожидаемыми» (awaitable), то есть реализовывать паттерн awaitable (о нём далее).

2. Их можно использовать только в асинхронных методах и асинхронных анонимных функциях. Даже внутри асинхронного метода нельзя использовать оператор await в анонимной функции, если она не обозначена как асинхронная.

3. Оператор await запрещён в небезопасном контексте. Это не означает, что вы не можете использовать небезопасный код в асинхронном методе; вы просто не можете использовать оператор await в этой части.

4. Запрещено использовать await внутри блокировки (lock). Если вам когда-нибудь потребуется блокировка ресурса на время выполнения асинхронной операции, вам следует изменить код. Не пытайтесь обойти ограничения компилятора, вызывая Monitor.TryEnter и Monitor.Exit вручную с помощью блока try/finally. Если это жизненно необходимо сделать, попробуйте использовать SemaphoreSlim с его метод WaitAsync.

Дело в том, что монитор, используемый оператором lock, может быть освобождён только тем же потоком, который первоначально его получил, в то время, как весьма вероятно, что поток, выполняющий код перед выражением await, будет отличаться от потока, выполняющего код после него. Либо во время ожидания первоначальный поток будет использован для выполнения какого-либо другого кода. По сути, оператор lock и асинхронность несовместимы.

5. Всегда было возможно использовать await в блоке try, который имеет только блок finally, а, следовательно, и в операторе using. Но до C#6 нельзя было использовать await в следующих блоках:

- try с блоком catch

- catch

- finally

Начиная с C#6 эти ограничения сняты.



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



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