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