Ответ 1
Microsoft приняла решение избежать как можно большего количества проблем с совместимостью при переносе async
в ASP.NET. И они хотели довести его до всех своих "один ASP.NET" - поддержка async
для WinForms, MVC, WebAPI, SignalR и т.д.
Исторически ASP.NET поддерживал чистые асинхронные операции с .NET 2.0 через асинхронный шаблон на основе событий (EAP), в котором асинхронные компоненты уведомляют SynchronizationContext
об их запуске и завершении..NET 4.5 приносит первые довольно значительные изменения в эту поддержку, обновляя основные асинхронные типы ASP.NET, чтобы лучше включить асинхронный шаблон на основе задач (TAP, т.е. async
).
Тем временем, каждая другая структура (WebForms, MVC и т.д.) разработала свой собственный способ взаимодействия с этим ядром, сохраняя обратную совместимость приоритетом. В попытке помочь разработчикам основной ASP.NET SynchronizationContext
был расширен за исключением того, что вы видите; он поймает много ошибок использования.
В мире WebForms они имеют RegisterAsyncTask
, но многие люди просто используют обработчики событий async void
. Таким образом, ASP.NET SynchronizationContext
позволит async void
в нужное время в течение жизненного цикла страницы, и если вы используете его в неподходящее время, это приведет к возникновению этого исключения.
В мире MVC/WebAPI/SignalR структуры более структурированы как сервисы. Таким образом, они смогли принять async Task
очень естественным образом, и рамочная структура должна иметь дело с возвращенной Task
- очень чистой абстракцией. В качестве дополнительной заметки вам больше не нужно AsyncController
; MVC знает, что он асинхронен только потому, что возвращает Task
.
Однако, если вы попытаетесь вернуть Task
и используйте async void
, который не поддерживается. И нет оснований для его поддержки; было бы довольно сложно просто поддерживать пользователей, которые в любом случае не должны этого делать. Помните, что async void
напрямую ссылается на основной ASP.NET SynchronizationContext
, полностью обходя рамки MVC. Структура MVC понимает, как ждать вашего Task
, но он даже не знает о async void
, поэтому он возвращает завершение в ядро ASP.NET, которое видит, что оно действительно не завершено.
Это может вызвать проблемы в двух сценариях:
- Вы пытаетесь использовать некоторую библиотеку или еще что-то, что использует
async void
. Извините, но очевидным фактом является то, что библиотека сломана и должна быть исправлена. - Вы переносите компонент EAP в
Task
и правильно используетеawait
. Это может вызвать проблемы, поскольку компонент EAP напрямую взаимодействует сSynchronizationContext
. В этом случае лучшим решением является изменение типа, чтобы он поддерживал TAP естественным образом или заменял его типом TAP (например,HttpClient
вместоWebClient
). В противном случае вы можете использовать TAP-over-APM вместо TAP-over-EAP. Если ни один из этих вариантов невозможен, вы можете просто использоватьTask.Run
вокруг своей обертки TAP-over-EAP.
Относительно "огня и забыть":
Я лично никогда не использую эту фразу для методов async void
. С одной стороны, семантика обработки ошибок, безусловно, не соответствует фразе "огонь и забыть" ; Я в шутку ссылаюсь на методы async void
как "огонь и авария". Истинный async
метод "огонь и забыть" будет async Task
, где вы игнорируете возвращенный Task
, а не ожидаете его.
Тем не менее, в ASP.NET вы почти никогда не хотите возвращаться с ранних запросов (что и подразумевает "огонь и забыть" ). Этот ответ уже слишком длинный, но у меня есть описание проблем в моем блоге, а также некоторый код для поддержки ASP.NET "огонь и забыть" , если это действительно необходимо.