Асинхронное программирование «растет» через базу кода. Это было по сравнению с зомби-вирусом . Лучшее решение - позволить ему расти, но иногда это невозможно.
Я написал несколько типов в моей Nito.AsyncEx библиотеке для работы с частично асинхронной базой кода. Однако нет решения, которое бы работало в любой ситуации.
Раствор А
Если у вас есть простой асинхронный метод, который не нуждается в синхронизации обратно в его контекст, тогда вы можете использовать Task.WaitAndUnwrapException
:
var task = MyAsyncMethod();
var result = task.WaitAndUnwrapException();
Вы не хотите использовать Task.Wait
или Task.Result
, потому что они включают исключения в AggregateException
.
Это решение подходит, только если MyAsyncMethod
не синхронизируется обратно с его контекстом. Другими словами, каждый await
в MyAsyncMethod
должен заканчиваться ConfigureAwait(false)
. Это означает, что он не может обновлять какие-либо элементы пользовательского интерфейса или обращаться к контексту запроса ASP.NET.
Раствор B
Если MyAsyncMethod
необходимо синхронизироваться обратно с его контекстом, то вы можете использовать AsyncContext.RunTask
для предоставления вложенного контекста:
var result = AsyncContext.RunTask(MyAsyncMethod).Result;
* Обновление от 14.04.2014: В более поздних версиях библиотеки API выглядит следующим образом:
var result = AsyncContext.Run(MyAsyncMethod);
(в этом примере можно использовать Task.Result
, потому что RunTask
будет распространять Task
исключений).
Причина, по которой вам может потребоваться AsyncContext.RunTask
вместо Task.WaitAndUnwrapException
, заключается в том, что в WinForms / WPF / SL / ASP.NET возможна довольно тонкая тупиковая ситуация:
- Синхронный метод вызывает асинхронный метод, получая
Task
.
- Синхронный метод блокирует ожидание на
Task
.
- Метод
async
использует await
без ConfigureAwait
.
-
Task
не может завершиться в этой ситуации, потому что он завершается только после завершения метода async
; метод async
не может завершиться, поскольку он пытается запланировать свое продолжение на SynchronizationContext
, а WinForms / WPF / SL / ASP.NET не разрешит запуск продолжения, поскольку синхронный метод уже запущен в этом контексте.
Это одна из причин, по которой рекомендуется использовать ConfigureAwait(false)
в каждом async
методе как можно больше.
Раствор C
AsyncContext.RunTask
не будет работать в каждом сценарии. Например, если метод async
ожидает чего-то, что требует события пользовательского интерфейса для завершения, то вы будете в тупике даже с вложенным контекстом. В этом случае вы можете запустить метод async
в пуле потоков:
var task = TaskEx.RunEx(async () => await MyAsyncMethod());
var result = task.WaitAndUnwrapException();
Однако для этого решения требуется MyAsyncMethod
, который будет работать в контексте пула потоков. Поэтому он не может обновлять элементы пользовательского интерфейса или обращаться к контексту запроса ASP.NET. И в этом случае вы также можете добавить ConfigureAwait(false)
к его await
операторам и использовать решение A.
Обновление, 2019-05-01: Текущие "наименьшие наихудшие практики" содержатся в статье MSDN здесь .