Асинхронное программирование «растет» через базу кода. Это было по сравнению с вирусом зомби . Лучшее решение - позволить ему расти, но иногда это невозможно.
Я написал несколько типов в моей библиотеке 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 здесь .