ASP.NET-controller: asynchronous модуль или обработчик завершен, пока асинхронная операция все еще находится на рассмотрении

У меня очень простой ASP.NET MVC 4 controller:

public class HomeController : Controller { private const string MY_URL = "http://smthing"; private readonly Task task; public HomeController() { task = DownloadAsync(); } public ActionResult Index() { return View(); } private async Task DownloadAsync() { using (WebClient myWebClient = new WebClient()) return await myWebClient.DownloadStringTaskAsync(MY_URL) .ConfigureAwait(false); } } 

Когда я запускаю проект, я вижу свое мнение, и все выглядит нормально, но когда я обновляю страницу, я получаю следующую ошибку:

[InvalidOperationException: asynchronous модуль или обработчик завершен, пока асинхронная операция еще не выполнена.]

Почему это происходит? Я сделал пару тестов:

  1. Если мы удалим task = DownloadAsync(); от конструктора и поместить его в метод Index он будет работать нормально без ошибок.
  2. Если мы используем другой return await Task.Factory.StartNew(() => { Thread.Sleep(3000); return "Give me an error"; }); DownloadAsync() return await Task.Factory.StartNew(() => { Thread.Sleep(3000); return "Give me an error"; }); он будет работать должным образом.

Почему невозможно использовать метод WebClient.DownloadStringTaskAsync внутри конструктора controllerа?

В Async Void, ASP.Net и Count of Outstanding Operations Стефан Клири объясняет корень этой ошибки:

Исторически сложилось так, что ASP.NET поддерживает чистые асинхронные операции с .NET 2.0 через asynchronous шаблон на основе событий (EAP), в котором асинхронные компоненты уведомляют SynchronizationContext об их запуске и завершении.

Что происходит, так это то, что вы запускаете DownloadAsync внутри своего конструктора classов, где внутри вас await вызов async http. Это регистрирует асинхронную операцию с ASP.NET SynchronizationContext . Когда ваш HomeController возвращается, он видит, что он ожидает ожидающую асинхронную операцию, которая еще не завершена, и именно поэтому она вызывает исключение.

Если мы удалим задачу = DownloadAsync (); от конструктора и поместить его в метод Index, он будет работать нормально без ошибок.

Как я объяснял выше, это потому, что у вас больше нет ожидающей асинхронной операции при возврате с controllerа.

Если мы используем другой Task.Factory.StartNew(() => { Thread.Sleep(3000); return "Give me an error"; }); DownloadAsync (), ожидаем Task.Factory.StartNew(() => { Thread.Sleep(3000); return "Give me an error"; }); он будет работать должным образом.

Это потому, что Task.Factory.StartNew делает что-то опасное в ASP.NET. Он не регистрирует выполнение задач с помощью ASP.NET. Это может привести к появлению красных случаев, когда выполняется повторный цикл пула, полностью игнорируя фоновые задачи, вызывая аномальное прерывание. Вот почему вы должны использовать механизм, который регистрирует задачу, например HostingEnvironment.QueueBackgroundWorkItem .

Вот почему невозможно делать то, что вы делаете, как вы это делаете. Если вы действительно хотите, чтобы это выполнялось в фоновом streamе, в стиле «огонь и забыть», используйте либо HostingEnvironment (если вы на .NET 4.5.2), либо BackgroundTaskManager . Обратите внимание, что, выполняя это, вы используете stream threadpool для выполнения операций async IO, что является избыточным и именно то, что async IO с попытками async-await .

Я столкнулся с проблемой. Клиент использует интерфейс, который возвращает Task и реализуется с помощью async.

В Visual Studio 2015 клиентский метод, который является асинхронным и не использует ключевое слово await при вызове метода, не получает никаких предупреждений или ошибок, код компилируется чисто. Состояние гонки продвигается к производству.

ASP.NET считает незаконным запуск «асинхронной операции», привязанной к ее SynchronizationContext и возврат ActionResult до завершения всех начатых операций. Все async методы регистрируются как «асинхронная операция», поэтому вы должны убедиться, что все такие вызовы, которые привязаны к компоненту SynchronizationContext ASP.NET, завершены до возвращения ActionResult .

В вашем коде вы возвращаетесь, не гарантируя, что DownloadAsync() запустится до завершения. Тем не менее, вы сохраняете результат в члене task , поэтому гарантировать, что это завершено, очень просто. Просто поставите await task во всех ваших методах действий (после их асинхронизации) до возврата:

 public async Task IndexAsync() { try { return View(); } finally { await task; } } 

РЕДАКТИРОВАТЬ:

В некоторых случаях вам может потребоваться вызвать метод async который не должен завершаться до возврата в ASP.NET . Например, вам может потребоваться лениво инициализировать задачу фоновой службы, которая должна продолжаться после завершения текущего запроса. Это не относится к коду OP, потому что OP хочет завершить задачу перед возвратом. Однако, если вам нужно начать и не ждать задания, есть способ сделать это. Вы просто должны использовать технику для «выхода» из текущего SynchronizationContext.Current .

  • ( не возобновляется ). Одна из Task.Run() заключается в том, чтобы избежать текущего контекста синхронизации. Тем не менее, люди рекомендуют не использовать это в ASP.NET, потому что threadpool для ASP.NET является особенным. Кроме того, даже вне ASP.NET этот подход приводит к дополнительному контекстному переключателю.

  • ( рекомендуется ) Безопасный способ избежать текущего контекста синхронизации, не заставляя дополнительный контекстный переключатель или беспокоиться о streamе streamа ASP.NET, заключается в том, чтобы установить SynchronizationContext.Current в null , вызвать ваш метод async и затем восстановить исходное значение .

Метод myWebClient.DownloadStringTaskAsync работает в отдельном streamе и не блокируется. Возможное решение – сделать это с помощью обработчика событий DownloadDataCompleted для myWebClient и поля classа SemaphoreSlim.

 private SemaphoreSlim signalDownloadComplete = new SemaphoreSlim(0, 1); private bool isDownloading = false; 

….

 //Add to DownloadAsync() method myWebClient.DownloadDataCompleted += (s, e) => { isDownloading = false; signalDownloadComplete.Release(); } isDownloading = true; 

 //Add to block main calling method from returning until download is completed if (isDownloading) { await signalDownloadComplete.WaitAsync(); } 

Одним из решений может быть async Task метода и ConfigureAwait(false) . Он будет действовать как async void и не продолжит контекст синхронизации (до тех пор, пока вы действительно не касаетесь конечного результата метода)

Пример уведомления по электронной почте с приложением.

 public async Task SendNotification(string SendTo,string[] cc,string subject,string body,string path) { SmtpClient client = new SmtpClient(); MailMessage message = new MailMessage(); message.To.Add(new MailAddress(SendTo)); foreach (string ccmail in cc) { message.CC.Add(new MailAddress(ccmail)); } message.Subject = subject; message.Body =body; message.Attachments.Add(new Attachment(path)); //message.Attachments.Add(a); try { message.Priority = MailPriority.High; message.IsBodyHtml = true; await Task.Yield(); client.Send(message); } catch(Exception ex) { ex.ToString(); } } 
  • Выбор между HttpClient и WebClient
  • Какая разница между classами WebClient и HTTPWebRequest в .NET?
  • Автоматически распаковывать ответ gzip через WebClient.DownloadData
  • Как изменить таймаут на объекте .NET WebClient
  • C # - Как сделать HTTP-вызов
  • Как заполнить формы и отправить с помощью Webclient в C #
  • Как получить строку json из url?
  • Символы в строке изменились после загрузки HTML из Интернета
  • Загрузите и загрузите двоичный файл на / из FTP-сервера в C # /. NET
  • Печально известная, но без ответа проблема загрузки файла, когда требуется защита Windows
  • C # WebClient отключить кеш
  • Давайте будем гением компьютера.