Страницы

Поиск по вопросам

пятница, 8 февраля 2019 г.

Разбор async await [дубликат]

На данный вопрос уже ответили: Нужен async/await или не нужен? 5 ответов Здравствуйте, извиняюсь за вопрос по этой теме т.к существует довольно огромное количество статей об этих ключевых словах, но возможно такое количество статей и ввело меня в некое не до понимание.
Я просто хочу узнать правильно ли я все понял и задать походу пару вопросов.
Начнем с async. Метод помеченный async разрешает нам внутри данного метода использовать await и требует что бы возвращаемый тип имел метод GetAwaiter. Ключевое слово await. Await ставится перед операцией которая выполняется во вторичном потоке. Он позволяет не дожидаться пока вторичный поток (IO или обычный Thread) закончит свое выполнение. Await просто оборачивает то, что идет после await в отдельный метод, который по callback вызывается когда отработает вторичный поток. Тем самым первичный поток продолжает выполняться дальше. А когда отработает вторичный поток из пула потоков возьмется рандомный поток который довыполняет вторую часть метода.
В итого что это дает в приложениях
в приложениях с ui потоками(не до конца понятно что за ui поток, работаю исключительно asp.net) наш поток продолжит выполнение и не заблочит действия пользователя; в asp.net mvc у меня первичный поток освободится и будет работать только вторичный; и последнее синтаксический сахар который скрывает всю логику которая это делает.
Вопросы
Правильно ли я все описал, действительно ли в этом смысл async await(разбить метод на две части и до выполнять вторую часть как callback, тем самым не задерживая первичный поток)? Что за метод configureawait? Почему многие говорят что эти ключевые слова не связаны с потоками. Ну как же await то ставится перед операцией, которая будет выполняться во вторичном потоке, да и суть то в чем, это конструкция была придумана что бы не держать первичный поток. Так что связь тут самая прямая или я не прав? Когда закончится выполняться вторичный поток, какой поток продолжит выполнять вторую часть метода. Тот самый вторичный поток? Или будет взят любой поток из thread pool?


Ответ

Для начала, ваши предположения.
Ну как бы технически говоря да. Но есть ещё одна важная для понимания вещь: с ключевым словом async функция разбиваются на куски так, что между этими кусками функция вообще нигде не бежит. А каждый из кусков выполняется синхронно, как обыкновенная функция. Границы этих кусков идут по await «Await ставится перед операцией которая выполняется во вторичном потоке.» — это неверно, await означает лишь, что в этой точке функция прекращает выполнение, и возобновляется тогда, когда Task, по отношению к которому производится await, завершается.
Task, как мы уже видели, вовсе не означает «кусок кода, бегущий в каком-то потоке». Например, async-функция производит Task, который бежит в конкретном потоке лишь иногда, кусками.

Теперь, ваши вопросы.
В общем и целом правда, но не на одну часть, а на много. Представьте себе, например, такой код:
async Task PrintSlowly(IEnumerable data) { Console.WriteLine("begin"); foreach (var n in data) { await Task.Delay(1000); Console.WriteLine(data); } Console.WriteLine("end"); }
В этом методе у нас k + 1 кусок, где k — количество элементов, полученных из IEnumerable Если await выполняется в UI-потоке (или в любом потоке, в котором есть свой TaskScheduler/SynchronizationContext), то следующий кусок функции перебрасывается для выполнения в тот же поток/SynchronizationContext. Если же вам это не нужно, вы можете сказать ConfigureAwait(false), что означает «мне безразлично, в каком контексте выполнятся остальные куски async-функции». При этом рантайм может заставить их выполняться где угодно, гарантий нет. Нет, операция, на которую делается await, не обязательно будет выполняться в другом потоке. Task не есть «кусок кода, который выполняется где-то ещё». Если эта операция реально не блокирует текущий поток, то да, текущий поток будет свободен. Но вы легко можете написать Task, который будет блокировать текущий поток:
async Task Weird() { if (Math.Sin(0) == 42) await Task.Delay(1000); for (int x = 0; x < 1_000_000; x++) for (int y = 0; y < 1_000_000; y++) for (int z = 0; z < 1_000_000; z++) { if (x * x * x * x + y * y * y * y == z * z * z * z) throw new Exception("Ферма лох"); } } Где и как выполняется Task, определяется исключительно самим Task'ом. Единственный случай, когда Task целиком выполняется на побочном потоке, есть Task, запущенный через Task.Run() (в этом случае поток берётся из пула потоков). В остальных случаях забота о том, где именно выполняться, лежит на самом Task'e, async/await этим не занимается, и даже не требует, чтобы Task вообще где-то выполнялся. Task должен бы завершаться, это да (и то не обязан), а выполняться он может вообще нигде. Вот пример:
static Task WaitTimer() { var tcs = new TaskCompletionSource(); new System.Threading.Timer(o => tcs.SetResult(0), null, 3000, System.Threading.Timeout.Infinite); return tcs.Task; }

И да, обязательно почитайте ответы к этому вопросу

Комментариев нет:

Отправить комментарий