ConfigureAwait when not awaiting

2019-04-16 05:04发布

I have an async method I am using to offload a few seconds' worth of fire-and-forget work so as not to slow down my page load. This work needs a bit of general setup and tidy-up; I want the (fast) setup to throw synchronously if it throws, but I don't want to force the tidy-up to run in the ASP context so I am using ConfigureAwait on the bit I am awaiting:

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

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

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

This seems odd, because

  • FireAndForgetAfterSetup shouldn't really care whether or not it is being called from an ASP context, so why should it have to be the one calling ConfigureAwait?
  • If I change my mind and decide that btn_Click should wait for FireAndForget to complete, has it already thrown away the ASP context(?)

Can someone explain to me if I'm misunderstanding?

3条回答
爱情/是我丢掉的垃圾
2楼-- · 2019-04-16 05:46

I'm not sure why he didn't post it, but my exact two questions are answered in this blog post by Stephen Cleary:

The important thing to note with this example is that each "level" of async method calls has its own context. DownloadFileButton_Click started in the UI context, and called DownloadFileAsync. DownloadFileAsync also started in the UI context, but then stepped out of its context by calling ConfigureAwait(false). The rest of DownloadFileAsync runs in the thread pool context. However, when DownloadFileAsync completes and DownloadFileButton_Click resumes, it does resume in the UI context.

A good rule of thumb is to use ConfigureAwait(false) unless you know you do need the context.

  • In answer to my first bullet point, yes, it's OK (encouraged!) to use ConfigureAwait(false) in library methods that know they won't use the context, because...
  • ... in answer to my second bullet point, even if the library async method has thrown away the UI context, the calling method still has its own copy. Thus the calling method could await the library method and resume in the UI context... but it's going to deadlock when that happens.
查看更多
萌系小妹纸
3楼-- · 2019-04-16 05:59

If your scenario is how to execute some (relatively) long-running task during load, ASP.NET allows this scenario through the Page.RegisterAsyncTask method. Scott Hansleman describes how to use it in The Magic of using Asynchronous Methods in ASP.NET 4.5 plus an important gotcha

Essentially, you create an asynchronous method that returns Task and call:

RegisterAsyncTask(new PageAsyncTask(MyAsyncMethod));

then call Page.ExecuteRegisteredAsyncTasks to start executing all registered tasks.

Scott Hanselman does a good job (of course) of describing why using an event handler, Task or background thread is a bad idea.

This is also described in "What Not to do in ASP.NET, What to do instead" in the "Asynchronous Page Events" section

查看更多
再贱就再见
4楼-- · 2019-04-16 06:08

The ASP.NET synchronization context doesn't allow fire-and-forget work items to be kicked off from within a request. The runtime actively monitors such things and will try to generate an exception since these code patterns lead to null refs, deadlocks, AVs, and other nastiness.

If you absolutely need to kick off fire-and-forget work in ASP.NET, consider using WebBackgrounder. It integrates with the ASP.NET extensibility points that are designed to allow for this. So it won't block the active request, but keep in mind Stephen's caveat: it's not ever guaranteed to be executed at all. If you require guaranteed execution, consider a reliability mechanism like Service Bus.

查看更多
登录 后发表回答