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:
- Why does it hang?
- Why do they have something called
TaskFriendlySynchronizationContext? (Given that it causes tasks to hang, I wouldn’t call it “friendly”)
- Is there a “best practice” for invoking
asyncmethods from page handler methods?
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));
}
The
TaskrepresentingPerformSleepingTaskis trying to resume after itsawaitso thatPerformSleepingTaskcan return. It is trying to re-enter the ASP.NET request context, which is blocked by the call toWait. This causes a deadlock, as I expound on my blog.To avoid the deadlock, follow these best practices:
asyncall the way down. Don’t block onasynccode.ConfigureAwait(false)in your “library” methods.TaskFriendlySynchronizationContextuses the newAspNetSynchronizationContext(the .NET 4.0TaskFriendlySynchronizationContexthas been renamedLegacyTaskFriendlySynchronizationContext), and it also uses a new,async-aware pipeline.I’m not 100% sure about this one, but I suspect that the reason
Page_Loadworks 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 (unlessPage.Asyncwasfalse).Absolutely. You can either just make your event handlers
async void, or useRegisterAsyncTask(new PageAsyncTask(...));. The first approach is easier but the second approach is favored by the ASP.NET team.