Про асинхронные методы в c#
я только начал изучать асинхронные методы и автор привел два примера:
await PrintNameAsync("Tom");
await PrintNameAsync("Bob");
await PrintNameAsync("Sam");
// определение асинхронного метода
async Task PrintNameAsync(string name)
{
await Task.Delay(3000); // имитация продолжительной работы
Console.WriteLine(name);
}
и
var tomTask = PrintNameAsync("Tom");
var bobTask = PrintNameAsync("Bob");
var samTask = PrintNameAsync("Sam");
await tomTask;
await bobTask;
await samTask;
// определение асинхронного метода
async Task PrintNameAsync(string name)
{
await Task.Delay(3000); // имитация продолжительной работы
Console.WriteLine(name);
}
причем второй код будет намного быстрее чем первый.Но в чем причина и какая разница между ними. Чем являются tomTask, bobTask и samTask и зачем им присвоили вызванные методы PrintNameAsync а затем в конце программы написали их с оператом await? Вообще не понимаю. Буду очень благодарен если кто-то сможет обьяснить мне по каким причинам такая разница в производительности и как данные строки кода работают.
Ответы (2 шт):
await PrintNameAsync("Tom")
ждёт (выполнение уходит наружу) окончания выполнения метода, а потом выполняется следующая команда await PrintNameAsync("Bob")
. Таким образом, методы выполняются последовательно.
В var tomTask = PrintNameAsync("Tom")
tomTask получает значение, срываясь с await Task.Delay(3000)
, то есть три строки выполняются быстро одна за другой, а соотвествующие таймеры Task.Delay(3000)
запустились. Потом await tomTask
ждёт выполнения задачи три секунды, await bobTask
и await samTask
уже ничего не ждут, так как в этих задачах три секунды уже истекли. Получается, что PrintNameAsync выполняются здесь параллельно.
Нужно пояснить, что оба фрагмента - методы async, такие же, как и PrintNameAsync. Если это упрощённый синтаксис, то подразумевается, что это код(ы) метода(ов) async Main.
Результат асинхронного метода возвращается после выполнения первого асинхронного вызова внутри этого метода с помощью await. Task - это класс со специальными свойствами (слово в общем смысле), который описывает задачу от момента её создания и до состояния после выполнения. Асинхронный метод, возвращающий Task, это специальная конструкция, в которой нет команды return с параметром. Такой метод возращает (после первого await) задачу - объект класса Task, которая описывает сам этот метод и имеет состояние "выполняется", а после завершения выполнения метода состояние задачи в объекте меняется на завершённое. Если асинхронный метод возвращает Task<T>, то он должен содержать return значения типа T, которое можно прочитать в возвращаемом объекте из свойства Result после завершения метода. Асинхронный метод может либо ничего не возвращать, либо только объект класса, имеющего метод GetAwaiter()
(одними из таких являются Task и Task<T>).
Всё просто, запуск любого метода - это скобочки ()
, в них могут быть или не быть аргументы. По скобочкам вы понимаете, где метод запущен.
Присваивание =
значения, возвращаемого из метода означает формирование переменной с результатом работы метода. Но для асинхронного метода это только первый этап получения результата.
При присваивании возвращается Task
или Task<T>
(есть и другие типы), в котором когда асинхронная операция завершится, появится результат T
, либо Task
просто просигналит о завершении операции. Для того чтобы дождаться окончания такой асинхронной операции, и при этом не заблокировать поток выполнения, потребуется await
.
Task
возвращается из метода грубо говоря сразу как только выполнение доходит до вызова дочерней асинхронной операции, то есть ключевого слова await
.
Вот наглядный пример полуасинхронного метода
// ПЛОХОЙ КОД, НЕ ДЕЛАЙТЕ ТАК
public async Task DoAsync()
{
Thread.Sleep(3000);
await Task.Delay(3000);
}
Если сделать так
Task t = DoAsync();
И засечь время, то эта строчка будет выполняться 3 секунды. Но как же так? Метод же асинхронный. А всё потому что в методе есть блокирующее ожидание до первого await
, то есть всё выше выполняется точно так же как в любом синхронном методе.
Если подождать
await t;
То на это уйдёт ещё 3 секунды, итого 6. Всё чётко.
Проиллюстрировал я вам это для того чтобы вы поняли, что строка
var tomTask = PrintNameAsync("Tom");
Выполнится моментально, так как await
внутри метода стоит в самом начале. То же самое с двумя следующими строчками. То есть запуск всех трёх методов произойдёт одновременно. В итоге все 3 метода выполнятся примерно за 3 секунды.
В первом же случае
await PrintNameAsync("Tom");
Этот самый await
делает 2 дела:
- Получает тот самый
Task
- Асинхронно ждёт его полного завершения
И это выполняется последовательно 3 раза. Поэтому код будет выполняться 3+3+3=9 секунд.
Ну и домашнее задание :) для ещё большего понимания
Вот 2 рабочих и совершенно идентичных по поведению асинхронных метода.
public async Task DoAsync1()
{
await Task.Delay(3000);
}
public Task DoAsync2()
{
return Task.Delay(3000);
}
Попробуйте выяснить сходство и разницу между этими двумя методами. Что будет, если в метод добавить вывод в консоль в начало или конец обоих методов? Найдите то, что невозможно сделать без async
и await
. Тогда станет ясно, для чего эти ключевые слова нужны.
Почитать ещё