Andrew Shepherd
Andrew Shepherd

Reputation: 45252

Confusing behaviour when invoking async methods inside ASP.NET

I created an ASP WebApplication using Visual Studio 2012.

If I modify the default page as follows:

public partial class _Default : Page
{
    static async Task PerformSleepingTask()
    {
        Action action = () =>
        {
            Thread.Sleep(TimeSpan.FromSeconds(0.5));
            int dummy = 3; // Just a nice place to put a break point
        };
        await Task.Run(action);
    }


    protected void Page_Load(object sender, EventArgs e)
    {
        Task performSleepingTask = PerformSleepingTask();
        performSleepingTask.Wait();
    }
}

On the call to performSleepingTask.Wait() it hangs indefinitely.


Interestingly, if I set this in the web.config:

<appSettings>

    <add key="aspnet:UseTaskFriendlySynchronizationContext" value="false" />
</appSettings>

Then it does work. The Wait function waits for the sleeping to finish on a different thread, then continues.


Can somebody explain:


Here's an implementation I came up with which works, but it feels like clumsy code:

    protected void Page_Load(object sender, EventArgs e)
    {
        ManualResetEvent mre = new ManualResetEvent(false);
        Action act = () =>
        {
            Task performSleepingTask = PerformSleepingTask();
            performSleepingTask.Wait();
            mre.Set();
        };
        act.BeginInvoke(null, null);
        mre.WaitOne(TimeSpan.FromSeconds(1.0));
    }

Upvotes: 2

Views: 667

Answers (1)

Stephen Cleary
Stephen Cleary

Reputation: 456527

Why does it hang?

The Task representing PerformSleepingTask is trying to resume after its await so that PerformSleepingTask can return. It is trying to re-enter the ASP.NET request context, which is blocked by the call to Wait. This causes a deadlock, as I expound on my blog.

To avoid the deadlock, follow these best practices:

  1. Use async all the way down. Don't block on async code.
  2. Use ConfigureAwait(false) in your "library" methods.

Why do they have something called TaskFriendlySynchronizationContext? (Given that it causes tasks to hang, I wouldn't call it "friendly")

TaskFriendlySynchronizationContext uses the new AspNetSynchronizationContext (the .NET 4.0 TaskFriendlySynchronizationContext has been renamed LegacyTaskFriendlySynchronizationContext), and it also uses a new, async-aware pipeline.

I'm not 100% sure about this one, but I suspect that the reason Page_Load works for the legacy SyncCtx is that the old pipeline wasn't putting the SyncCtx in place yet. I'm not sure why it would act this way, though (unless Page.Async was false).

Is there a "best practice" for invoking async methods from page handler methods?

Absolutely. You can either just make your event handlers async void, or use RegisterAsyncTask(new PageAsyncTask(...));. The first approach is easier but the second approach is favored by the ASP.NET team.

Upvotes: 4

Related Questions