3

I've been looking online and am have some questions that hopefully someone here can answer for me. I'm trying to solve a problem that's caused IIS crashes for us. This service call is supposed to be a fire and forget type of call. If it fails we suppress and log the exception. Because it's a long running service call it was implemented as asynchronous. The current implementation has a few nested method calls as follow:

// in first service
public void PerformService(some params)
{
    // setup and validation

    DoAction();
}
private void DoAction()
{
    // final setup

    OtherService.FireAndForgetAsync(some params);
}

// in the other service (OtherService)
public async Task FireAndForgetAsync()
{
    await Task.Run(() => 
    {
        try 
        {
           // do some long running stuff
        }
        catch (Exception e)
        {
            ErrorLogger.LogError(e)
        }
    }
}

Effectively we're in a loop (from 0 to many iterations) calling PerformService and currently getting the following exception:

System.NullReferenceException: Object reference not set to an instance of an object. at   
System.Web.ThreadContext.AssociateWithCurrentThread(Boolean setImpersonati onContext) at 
System.Web.HttpApplication.OnThreadEnterPrivate(Boolean setImpersonationCo ntext) at
System.Web.LegacyAspNetSynchronizationContext.CallCallbackPossiblyUnderLock(SendOrPostCallback callback, Object state) at System.Web.LegacyAspNetSynchronizationContext.CallCallback(SendOrPostCallb ack callback, Object state) at System.Web.LegacyAspNetSynchronizationContext.Post(SendOrPostCallback call back, Object state) at System.Threading.Tasks.SynchronizationContextAwaitTaskContinuation.PostAct ion(Object state) at System.Threading.Tasks.AwaitTaskContinuation.RunCallback(ContextCallback c allback, Object

Even when I've forced an exception to happen in the try catch block (to test our error logger), it's properly caught and logged by our error logger. It looks like some thread is still trying to do SynchronizationContext.Post after the request is completed. I'm thinking we're improperly using C# 5's async/await in the sense that FireAndForgetAsync is doing an await, but nothing else is. So, we're saying we're going to wait but then just leave. The http request ends and by then it can't synchronize itself back up since there's nothing left to synchronize to. Not good since this full on crashes IIS. I'm mainly looking for other people's input since I'm not very familiar with the async/await in C# 5 and of what a proper solution would be. Since we don't want to wait for it to finish, removing the await and async modifier from the FireAndForgetAsync method seems like it should fix the problem.

Nestor
  • 165
  • 2
  • 17

2 Answers2

6

You shouldn't be doing the await unless you have something to do after the Task.Run lambda completes. By doing the await, you're essentially requesting that the context be restored to essentially do a no-op before the returned Task completes.

If you had a reason to do an await (i.e. some work that should be done after the Task.Run lambda finished), and you didn't want it to attempt to restore the context, then you could use ConfigureAwait(false) on the Task returned from Task.Run.

See this post for a great explanation:

Community
  • 1
  • 1
Matt Smith
  • 17,026
  • 7
  • 53
  • 103
6

This service call is supposed to be a fire and forget type of call.

Hopefully you don't have any important code in there, because IIS wasn't designed to be used like this.

[Stack trace] ... LegacyAspNetSynchronizationContext ...

Red flag! LegacyAspNetSynchronizationContext doesn't play perfectly with async and await.

Please ensure you have the following setting in your web.config:

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

If you are doing actual business logic in FireAndForgetAsync, then you should change your architecture: have DoAction post work to persistent storage (e.g., an Azure queue) and have an independent, non-IIS service (e.g., an Azure worker role) process the work in the queue.

If your FireAndForgetAsync work is not important, then you can use something like the BackgroundTaskManager type on my blog to mitigate the problems of this approach ...

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810