Асинхронне програмування "росте" через базу коду. Його порівнювали з вірусом зомбі . Найкраще рішення - дозволити йому рости, але іноді це неможливо.
Я написав кілька типів у своїй бібліотеці 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:
- Синхронний метод викликає метод асинхронізації, отримуючи a
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 .