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
метод библиотеки и возобновление в контексте пользовательского интерфейса... но это произойдет в тупик, когда это произойдет.