Зачем использовать async, когда мне нужно будет ждать?

Я застрял в этом вопросе некоторое время и на самом деле не нашел полезных разъяснений относительно того, почему это так.

Если у меня есть метод async, например:

public async Task<bool> MyMethod()
{
    // Some logic
    return true;
}

public async void MyMethod2()
{
    var status = MyMethod(); // Visual studio green lines this and recommends using await
}

Если я использую await здесь, какая точка асинхронного метода? Разве это не делает async бесполезным, что VS говорит мне называть await? Означает ли это, что это не значит, что задача разгрузки задачи в поток не дожидается завершения?

Ответ 1

Если я использую здесь ожидание, какая точка асинхронного метода?

await не блокирует поток. MyMethod2 будет работать синхронно, пока не достигнет выражения await. Затем MyMethod2 будет приостановлено до завершения ожидаемой задачи (MyMethod). Пока MyMethod не будет завершено, управление вернется к вызывающему абоненту MyMethod2. То, что точка await - вызывающий будет продолжать выполнять эту работу.

Разве это не делает асинк бесполезным, что VS говорит мне, чтобы он ждал?

async - это просто флаг, который означает "где-то в методе, который у вас есть один или несколько ожиданий".

Неужели это не побеждает цель разгрузки задачи на поток? не дожидаясь его завершения?

Как описано выше, вам не нужно ждать завершения задачи. Здесь ничего не блокируется.

ПРИМЕЧАНИЕ. Чтобы следовать стандартам именования рамок, я предлагаю вам добавить суффикс async к именам асинхронных методов.

Ответ 2

Неужели это не побеждает цель выгрузки задачи в поток, не дожидаясь ее завершения?

Да, конечно. Но это не цель ожидания/асинхронности. Цель состоит в том, чтобы разрешить вам писать синхронный код, который использует асинхронные операции, не тратя нити, или, в более общем плане, дать вызывающему устройству меру контроля над более или менее асинхронными операциями.

Основная идея заключается в том, что до тех пор, пока вы будете использовать await и async правильно, вся операция будет синхронной. Обычно это хорошо, потому что большинство вещей, которые вы делаете, синхронны - скажем, вы не хотите создавать пользователя перед запросом имени пользователя. Итак, вы сделали бы что-то вроде этого:

var name = await GetNameAsync();
var user = await RemoteService.CreateUserAsync(name);

Две операции синхронны друг относительно друга; второй не (и не может!) случиться до первого. Но они не являются (обязательно) синхронными по отношению к их вызывающему. Типичным примером является приложение Windows Forms. Представьте, что у вас есть кнопка, а обработчик кликов содержит код выше - весь код работает в потоке пользовательского интерфейса, но в то же время, когда вы await ing, поток пользовательского интерфейса может выполнять другие задачи (аналогично до использования Application.DoEvents до завершения операции).

Синхронный код легче писать и понимать, поэтому это позволяет получить большую часть преимуществ асинхронных операций, не делая ваш код сложнее понять. И вы не теряете способность делать вещи асинхронно, так как Task сам по себе является просто обещанием, и вам не всегда нужно его await сразу. Представьте себе, что GetNameAsync занимает много времени, но в то же время у вас есть работа с ЦП, прежде чем это сделать:

var nameTask = GetNameAsync();

for (int i = 0; i < 100; i++) Thread.Sleep(100); // Important busy-work!

var name = await nameTask;
var user = await RemoteService.CreateUserAsync(name);

И теперь ваш код по-прежнему красиво синхронный - await - это точка синхронизации - в то время как вы можете делать другие вещи параллельно с асинхронными операциями. Другим типичным примером является одновременное отключение нескольких асинхронных запросов, но сохранение кода синхронно с завершением всех запросов:

var tasks = urls.Select(i => httpClient.GetAsync(i)).ToArray();

await Task.WhenAll(tasks);

Задачи являются асинхронными по отношению друг к другу, но не их вызывающим, что по-прежнему красиво синхронно.

Я сделал (неполный) сетевой пример, который использует ожидание именно таким образом. Основная идея заключается в том, что, хотя большая часть кода логически синхронна (там должен следовать протокол), попросите login- > подтвердить login- > read loop..., вы даже можете увидеть ту часть, где параллельно ожидаются несколько задач), вы используете только поток, когда у вас действительно есть работа с ЦП. Ожидание делает это почти тривиальным - делать то же самое с продолжением, или старая асинхронная модель Begin/End будет гораздо более болезненной, особенно в отношении обработки ошибок. Ожидание делает его очень чистым.

Ответ 3

Асинхронный метод не выполняется автоматически в другом потоке. Фактически, верно обратное: в вызывающем потоке всегда выполняется метод async. async означает, что это метод, который может привести к асинхронной операции. Это означает, что он может вернуть управление вызывающему абоненту в ожидании завершения другого выполнения. Таким образом, методы asnync - это способ ожидания других асинхронных операций.

Поскольку вы ничего не можете ждать в MyMethod2, async не имеет смысла здесь, поэтому ваш компилятор предупреждает вас.

Интересно, что команда, которая реализовала методы async, признала, что маркировка метода async на самом деле не нужна, так как было бы достаточно просто использовать await в кубе метода, чтобы компилятор распознал его как async, Требование использования ключевого слова async было добавлено во избежание нарушения существующего кода, который использует await как имя переменной.