Я не совсем понимаю разницу между Task.Wait
иawait
.
У меня есть что-то похожее на следующие функции в службе ASP.NET WebAPI:
public class TestController : ApiController
{
public static async Task<string> Foo()
{
await Task.Delay(1).ConfigureAwait(false);
return "";
}
public async static Task<string> Bar()
{
return await Foo();
}
public async static Task<string> Ros()
{
return await Bar();
}
// GET api/test
public IEnumerable<string> Get()
{
Task.WaitAll(Enumerable.Range(0, 10).Select(x => Ros()).ToArray());
return new string[] { "value1", "value2" }; // This will never execute
}
}
Где Get
будет тупик.
Что может вызвать это? Почему это не вызывает проблемы, когда я использую блокирующее ожидание, а не await Task.Delay
?
Task.Delay(1).Wait()
что достаточно хорошо.Task.Delay(1).Wait()
в основном то же самое, что иThread.Sleep(1000)
. В реальном производственном коде это редко уместно.WaitAll
вызывает тупик. Смотрите ссылку на мой блог в моем ответе для более подробной информации. Вы должны использоватьawait Task.WhenAll
вместо этого.ConfigureAwait(false)
в единый вызовBar
илиRos
не тупик, а потому , что у вас есть перечислимых , что создает больше , чем один , а затем ждать на всех тех, первая полоса будет тупиковой второй. Если выawait Task.WhenAll
вместо того, чтобы ждать выполнения всех задач, чтобы не блокировать контекст ASP, вы увидите, что метод возвращает нормально..ConfigureAwait(false)
весь путь вверх по дереву, пока вы не заблокируете, чтобы ничто не пыталось вернуться к основному контексту; это будет работать. Другим вариантом будет ускорение внутреннего контекста синхронизации. Link . Если вы вставитеTask.WhenAll
в него,AsyncPump.Run
он будет эффективно блокировать все это без необходимостиConfigureAwait
где-либо, но это, вероятно, слишком сложное решение.Ответы:
Wait
иawait
- хотя схожи концептуально - на самом деле совершенно разные.Wait
будет синхронно блокироваться, пока задача не будет завершена. Таким образом, текущий поток буквально блокируется в ожидании завершения задачи. Как правило, вы должны использовать «async
весь путь вниз»; то есть не блокируйтеasync
код. В своем блоге я подробно расскажу о том, как блокировка в асинхронном коде вызывает тупик .await
будет асинхронно ждать, пока задача не завершится. Это означает, что текущий метод "приостановлен" (его состояние фиксируется) и метод возвращает незавершенную задачу вызывающей стороне. Позже, когдаawait
выражение завершится, остаток метода планируется как продолжение.Вы также упомянули «кооперативный блок», под которым я предполагаю, что вы имеете в виду задачу, которую вы
Wait
выполняете, может выполняться в ожидающем потоке. Есть ситуации, когда это может произойти, но это оптимизация. Есть много ситуаций, когда это не может произойти, например, если задача предназначена для другого планировщика, или если она уже запущена, или если это не кодовая задача (например, в вашем примере кода:Wait
невозможно выполнитьDelay
задачу встроенным, потому что нет кода для этого).Вы можете найти мое
async
/await
вступление полезным.источник
Wait
отлично работаетawait
тупиков.await Task.Delay(1)
сTask.Delay(1).Wait()
сервисом работает нормально, в противном случае это ТУПИКИ.Wait
блокирует поток, и его нельзя использовать для других целей.await
кодом. Либо это, либо тупик не был связан ни с чем, и вы неправильно диагностировали проблему.SynchronizationContext
блокировку в запросе ASP.NET Core, которая больше не блокируется.На основании того, что я прочитал из разных источников:
await
Выражение не блокирует нить , на которой он выполняется. Вместо этого он заставляет компилятор зарегистрировать оставшуюся частьasync
метода как продолжение ожидаемой задачи. Затем управление возвращается к вызывающей сторонеasync
метода. Когда задача завершается, она вызывает ее продолжение, и выполнениеasync
метода возобновляется с того места, где оно было остановлено.Чтобы дождаться
task
завершения сингла , вы можете вызвать егоTask.Wait
метод. ВызовWait
метода блокирует вызывающий поток до тех пор, пока один экземпляр класса не завершит выполнение. Метод без параметровWait()
используется для безусловного ожидания завершения задачи. Задача имитирует работу, вызываяThread.Sleep
метод для сна в течение двух секунд.Эта статья также хорошо читается.
источник
Некоторые важные факты не были приведены в других ответах:
«async await» более сложен на уровне CIL и, следовательно, стоит памяти и процессорного времени.
Любая задача может быть отменена, если время ожидания недопустимо.
В случае «асинхронного ожидания» у нас нет обработчика для такой задачи, чтобы отменить его или отслеживать его.
Использование Task более гибкое, чем «async await».
Любые функции синхронизации могут быть упакованы с помощью async.
«асинхронное ожидание» порождает много проблем. Мы пока не ожидаем, что заявление будет достигнуто без выполнения и отладки контекста. Если первое ожидание не достигнуто, все блокируется . Иногда даже кажется, что ожидание достигнуто, но все заблокировано:
https://github.com/dotnet/runtime/issues/36063
Я не понимаю, почему я должен жить с дублированием кода для синхронизации и асинхронного метода или использования хаков.
Вывод: создавать задачи вручную и контролировать их гораздо лучше. Обработчик задачи дает больше контроля. Мы можем отслеживать задачи и управлять ими:
https://github.com/lsmolinski/MonitoredQueueBackgroundWorkItem
Извините за мой английский.
источник