Асинхронное программирование «растет» через базу кода. Это было по сравнению с вирусом зомби . Лучшее решение - позволить ему расти, но иногда это невозможно.
Я написал несколько типов в моей библиотеке Nito.AsyncEx для работы с частично асинхронной базой кода. Там нет решения, которое работает в любой ситуации, хотя.
Решение А
Если у вас есть простой асинхронный метод, который не нуждается в синхронизации обратно в его контекст, то вы можете использовать Task.WaitAndUnwrapException:
var task = MyAsyncMethod();
var result = task.WaitAndUnwrapException();
Вы не хотите использовать Task.Waitили Task.Resultпотому что они обертывают исключения в AggregateException.
Это решение подходит только в том случае, если MyAsyncMethodне выполняется обратная синхронизация с его контекстом. Другими словами, каждый awaitв MyAsyncMethodдолжен заканчиваться ConfigureAwait(false). Это означает, что он не может обновлять какие-либо элементы пользовательского интерфейса или обращаться к контексту запроса ASP.NET.
Решение Б
Если 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метода.
Решение С
AsyncContext.RunTaskне будет работать в каждом сценарии. Например, если asyncметод ожидает чего-то, что требует события пользовательского интерфейса для завершения, то вы зашли в тупик даже с вложенным контекстом. В этом случае вы можете запустить asyncметод в пуле потоков:
var task = Task.Run(async () => await MyAsyncMethod());
var result = task.WaitAndUnwrapException();
Однако это решение требует, MyAsyncMethodчтобы он работал в контексте пула потоков. Поэтому он не может обновлять элементы пользовательского интерфейса или обращаться к контексту запроса ASP.NET. И в этом случае вы можете также добавить ConfigureAwait(false)к его awaitзаявлениям и использовать решение А.
Обновление, 2019-05-01: Текущие «наихудшие практики» описаны в статье MSDN здесь .