На самом деле, async / await не так волшебно. Полная тема довольно обширна, но я думаю, что для быстрого, но достаточно полного ответа на ваш вопрос мы справимся.
Давайте рассмотрим простое событие нажатия кнопки в приложении Windows Forms:
public async void button1_Click(object sender, EventArgs e)
{
Console.WriteLine("before awaiting");
await GetSomethingAsync();
Console.WriteLine("after awaiting");
}
Я собираюсь явно не говорить о том, что он GetSomethingAsync
сейчас возвращает. Скажем так, это то, что завершится, скажем, через 2 секунды.
В традиционном, не асинхронном мире ваш обработчик событий нажатия кнопки будет выглядеть примерно так:
public void button1_Click(object sender, EventArgs e)
{
Console.WriteLine("before waiting");
DoSomethingThatTakes2Seconds();
Console.WriteLine("after waiting");
}
Когда вы нажмете кнопку в форме, приложение будет зависать примерно на 2 секунды, пока мы ждем завершения этого метода. То, что происходит, - то, что "насос сообщений", в основном петля, заблокирован.
Этот цикл непрерывно спрашивает окна: «Кто-нибудь что-то сделал, например, переместил мышь, что-то щелкнул? Нужно ли что-то перекрашивать? Если так, скажите мне!» а затем обрабатывает это «что-то». Этот цикл получил сообщение о том, что пользователь нажал «button1» (или эквивалентный тип сообщения из Windows), и в итоге вызвал наш button1_Click
метод выше. Пока этот метод не возвращается, этот цикл застрял в ожидании. Это занимает 2 секунды, и в течение этого сообщения не обрабатываются.
Большинство вещей, которые имеют дело с окнами, выполняется с помощью сообщений, что означает, что если цикл обработки сообщений прекращает прокачивать сообщения, даже на секунду, он быстро заметен пользователем. Например, если вы переместите блокнот или любую другую программу поверх вашей собственной программы, а затем снова уйдете, в вашу программу будет отправлен поток сообщений рисования, указывающих, какая область окна, которая теперь внезапно снова стала видимой. Если цикл обработки сообщений обрабатывает что-то заблокированное, то рисование не выполняется.
Итак, если в первом примере async/await
не создаются новые темы, как это происходит?
Хорошо, что происходит, что ваш метод разделен на две части. Это один из тех общих вопросов, поэтому я не буду вдаваться в подробности, но достаточно сказать, что метод разбит на две вещи:
- Весь код, ведущий к
await
, включая вызовGetSomethingAsync
- Весь код следующий
await
Иллюстрация:
code... code... code... await X(); ... code... code... code...
Переставленные:
code... code... code... var x = X(); await X; code... code... code...
^ ^ ^ ^
+---- portion 1 -------------------+ +---- portion 2 ------+
В основном метод выполняется так:
- Он выполняет все до
await
Он вызывает GetSomethingAsync
метод, который делает свое дело, и возвращает что-то, что завершит 2 секунды в будущем
Пока что мы все еще в исходном вызове button1_Click, происходящем в основном потоке, который вызывается из цикла сообщений. Если выполнение кода await
занимает много времени, пользовательский интерфейс все равно будет зависать. В нашем примере не так много
Что await
ключевое слово, вместе с каким - то умным магии компилятора, делает то , что это в основном что - то вроде «Хорошо, вы знаете , что я собираюсь просто вернуться из щелчка кнопки обработчика событий здесь. Если вы (как, вещей мы» "жду завершения), дайте мне знать о завершении, дайте мне знать, потому что у меня еще есть код для выполнения".
На самом деле он сообщит классу SynchronizationContext о том, что это сделано, и, в зависимости от текущего контекста синхронизации, который находится в данный момент в игре, будет поставлен в очередь на выполнение. Класс контекста, используемый в программе Windows Forms, поставит его в очередь, используя очередь, которую качает цикл сообщений.
Таким образом, он возвращается обратно к циклу сообщений, который теперь может продолжать качать сообщения, такие как перемещение окна, изменение его размера или нажатие других кнопок.
Для пользователя пользовательский интерфейс теперь снова реагирует, обрабатывая другие нажатия кнопок, изменяя размеры и, самое главное, перерисовывая , чтобы он не зависал.
- Через 2 секунды мы ожидаем завершения, и теперь происходит то, что он (ну, контекст синхронизации) помещает сообщение в очередь, на которую смотрит цикл обработки сообщений, говоря: «Эй, я получил еще немного кода для вам выполнить ", и этот код весь код после ожидания.
- Когда цикл сообщений попадает в это сообщение, он в основном «повторно вводит» тот метод, в котором он остановился, сразу после
await
и продолжает выполнение остальной части метода. Обратите внимание, что этот код снова вызывается из цикла сообщений, поэтому, если этот код делает что-то длинное без async/await
правильного использования , он снова заблокирует цикл сообщений.
Есть много движущихся частей под капотом здесь , так что здесь некоторые ссылки на дополнительную информацию, я собирался сказать «вам это нужно», но эта тема является достаточно широким , и это довольно важно знать некоторые из этих движущихся частей . Неизменно вы поймете, что async / await все еще является утечкой. Некоторые из базовых ограничений и проблем все еще проникают в окружающий код, и если они этого не делают, вам обычно приходится отлаживать приложение, которое ломается случайным образом, по-видимому, без веской причины.
Хорошо, а что если GetSomethingAsync
закрутить поток, который завершится через 2 секунды? Да, тогда очевидно, что есть новая тема в игре. Этот поток, однако, не из- за асинхронности этого метода, а потому, что программист этого метода выбрал поток для реализации асинхронного кода. Почти все асинхронные операции ввода-вывода не используют поток, они используют разные вещи. async/await
сами по себе не раскручивают новые потоки, но, очевидно, «вещи, которые мы ждем», могут быть реализованы с использованием потоков.
В .NET есть много вещей, которые не обязательно ускоряют поток сами по себе, но по-прежнему асинхронны:
- Веб-запросы (и многие другие связанные с сетью вещи, которые требуют времени)
- Асинхронное чтение и запись файлов
- и многое другое, хороший знак - если у рассматриваемого класса / интерфейса есть методы с именами
SomethingSomethingAsync
или BeginSomething
и, EndSomething
и в этом IAsyncResult
участвует.
Обычно эти вещи не используют нить под капотом.
Итак, вы хотите что-то из этого "широкого материала темы"?
Что ж, давайте спросим попробуйте Roslyn о нашем нажатии кнопки:
Попробуйте Рослин
Я не собираюсь ссылаться на полностью сгенерированный класс здесь, но это довольно ужасные вещи.