Про асинхронные методы в 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 шт):

Автор решения: rotabor

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>).

→ Ссылка
Автор решения: aepot

Всё просто, запуск любого метода - это скобочки (), в них могут быть или не быть аргументы. По скобочкам вы понимаете, где метод запущен.

Присваивание = значения, возвращаемого из метода означает формирование переменной с результатом работы метода. Но для асинхронного метода это только первый этап получения результата.

При присваивании возвращается 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. Тогда станет ясно, для чего эти ключевые слова нужны.


Почитать ещё

→ Ссылка