ConfigureAwait, когда не ждет

У меня есть async метод, который я использую, чтобы снять нагрузку с работы на несколько секунд, чтобы не замедлить загрузку моей страницы. Эта работа требует немного общей настройки и опрятности; Я хочу, чтобы (быстрая) установка генерировала синхронно, если она выдает, но я не хочу принудительно запускать уборку в контексте ASP, поэтому я использую ConfigureAwait немного жду:

public Task FireAndForget()
{
    DoSetup();
    return FireAndForgetAfterSetup();
}

private async Task FireAndForgetAfterSetup()
{
    await AFewSecondsWorthOfWork().ConfigureAwait(false);
    DoTidyUp();
}

protected void btn_Click(object sender, EventArgs e)
{
    FireAndForget();
}

Это кажется странным, потому что

  • FireAndForgetAfterSetup на самом деле не должно волновать, вызывается ли он из контекста ASP, так почему он должен быть вызывающим ConfigureAwait?
  • Если я передумаю и решу, что btn_Click должен ждать FireAndForget чтобы завершить, он уже выбросил контекст ASP (?)

Может кто-нибудь объяснить мне, если я неправильно понимаю?

3 ответа

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

Если вам абсолютно необходимо начать работу в ASP.NET, то подумайте об использовании WebBackgrounder. Он интегрируется с точками расширения ASP.NET, которые предназначены для этого. Поэтому он не будет блокировать активный запрос, но имейте в виду предостережение Стивена: его выполнение никогда не гарантируется. Если вам требуется гарантированное выполнение, рассмотрите механизм надежности, такой как Service Bus.

Если ваш сценарий заключается в том, как выполнить некоторую (относительно) длительную задачу во время загрузки, ASP.NET разрешает этот сценарий с помощью метода Page.RegisterAsyncTask. Скотт Хэнслман описывает, как использовать его в "Волшебстве использования асинхронных методов в ASP.NET 4.5 плюс один важный момент"

По сути, вы создаете асинхронный метод, который возвращает Task и вызывает:

RegisterAsyncTask(new PageAsyncTask(MyAsyncMethod));

затем вызовите Page.ExecuteRegisteredAsyncTasks, чтобы начать выполнение всех зарегистрированных задач.

Скотт Хансельман (конечно, хорошо) объясняет, почему использование обработчика событий, задачи или фонового потока - плохая идея.

Это также описано в разделе " Что не нужно делать в ASP.NET, что делать вместо этого " в разделе "Асинхронные события страницы"

Я не уверен, почему он не опубликовал это, но на мои два точных вопроса в этом сообщении ответил Стивен Клири:

В этом примере важно отметить, что каждый "уровень" вызовов асинхронных методов имеет свой собственный контекст. DownloadFileButton_Click запускается в контексте пользовательского интерфейса и называется DownloadFileAsync. DownloadFileAsync также запускался в контексте пользовательского интерфейса, но затем выходил из его контекста, вызывая ConfigureAwait(false). Остальная часть DownloadFileAsync выполняется в контексте пула потоков. Однако после завершения DownloadFileAsync и возобновления DownloadFileButton_Click оно возобновляется в контексте пользовательского интерфейса.

Хорошее практическое правило - использовать ConfigureAwait (false), если вы не знаете, что вам нужен контекст.

  • В ответ на мой первый пункт, да, это нормально (рекомендуется!) Для использования ConfigureAwait(false) в библиотечных методах, которые знают, что они не будут использовать контекст, потому что...
  • ... в ответ на мой второй пункт пули, даже если библиотека async Метод отбросил контекст пользовательского интерфейса, вызывающий метод все еще имеет свою собственную копию. Таким образом, вызывающий метод может await метод библиотеки и возобновление в контексте пользовательского интерфейса... но это произойдет в тупик, когда это произойдет.
Другие вопросы по тегам