1

I have Web API method that calls another method marked with async used to update a database (using EF 6). I do not need to wait on the db method to complete (its fire and forget), hence I do not use await when calling this async method. If I do not call await, a NullReferenceException is thrown that is never passed to my code, and just shows up as a first chance exception in the output window of VS2013.

What is the proper way to handle calling an async method without await-ing?

Below is an example of what I am doing:

data repo method:

public async Task UpdateSessionCheckAsync(int sessionId, DateTime time)
    {

        using (MyEntities context = new MyEntities())
        {
            var session = await context.Sessions.FindAsync(sessionId);

            if (session != null)
                session.LastCheck = time;

            await context.SaveChangesAsync();
        }

    }

Web api repository method:

public async Task<ISession> GetSessionInfoAsync(int accountId, int siteId, int visitorId, int sessionId)
    {
        //some type of validation

        var session =await  GetValidSession(accountId, siteId, visitorId, sessionId);

        DataAdapter.UpdateSessionCheckAsync(sessionId, DateTime.UtcNow); // this is the line causing the exception if not awaited

        return new Session
        {
            Id = sessionId,
            VisitorId = session.VisitorId
        };

    }

Web API Method:

 [ResponseType(typeof(Session))]
    public async Task<IHttpActionResult> GetSessionInfo(HttpRequestMessage request, int accountId, int siteId, int visitorId, int sessionId)
    {
            var info = await _repository.GetSessionInfoAsync(accountId, siteId, visitorId, sessionId);

            return Ok(info);
    }

and finally the stack trace I get:

    System.Web.dll!System.Web.ThreadContext.AssociateWithCurrentThread(bool setImpersonationContext)
System.Web.dll!System.Web.HttpApplication.OnThreadEnterPrivate(bool setImpersonationContext)
System.Web.dll!System.Web.HttpApplication.System.Web.Util.ISyncContext.Enter()
System.Web.dll!System.Web.Util.SynchronizationHelper.SafeWrapCallback(System.Action action)
System.Web.dll!System.Web.Util.SynchronizationHelper.QueueAsynchronous.AnonymousMethod__7(System.Threading.Tasks.Task _)
mscorlib.dll!System.Threading.Tasks.ContinuationTaskFromTask.InnerInvoke()
mscorlib.dll!System.Threading.Tasks.Task.Execute()
mscorlib.dll!System.Threading.Tasks.Task.ExecutionContextCallback(object obj)
mscorlib.dll!System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state, bool preserveSyncCtx)
mscorlib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state, bool preserveSyncCtx)
mscorlib.dll!System.Threading.Tasks.Task.ExecuteWithThreadLocal(ref System.Threading.Tasks.Task currentTaskSlot)
mscorlib.dll!System.Threading.Tasks.Task.ExecuteEntry(bool bPreventDoubleExecution)
mscorlib.dll!System.Threading.Tasks.Task.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem()
mscorlib.dll!System.Threading.ThreadPoolWorkQueue.Dispatch()
mscorlib.dll!System.Threading._ThreadPoolWaitCallback.PerformWaitCallback()

edit: Ive changed my db method to return void instead of task, and then used Task.Factory to start a new task calling the db method and the exception is gone.

public async Task<ISession> GetSessionInfoAsync(int accountId, int siteId, int visitorId, int sessionId)
    {
        //some type of validation

        var session =await  GetValidSession(accountId, siteId, visitorId, sessionId);

        Task.Factory.StartNew(() => DataAdapter.UpdateSessionCheckAsync(sessionId, DateTime.UtcNow));

        return new Session
        {
            Id = sessionId,
            VisitorId = session.VisitorId
        };

    }
Mike_G
  • 16,237
  • 14
  • 70
  • 101

1 Answers1

5

I do not need to wait on the db method to complete (its fire and forget)

The first thing I always do when I see this is ask: are you absolutely sure you want to do this?

Bear in mind that "fire and forget" on ASP.NET is really just the same as saying "I don't care if this code is ever actually executed".

Since you're just updating a "last check" time, it's possible that fire-and-forget is what you want; just make note that this means your last check time may or may not actually be correct.

That said, you can register your work with the ASP.NET runtime as I describe on my blog:

public async Task<ISession> GetSessionInfoAsync(int accountId, int siteId, int visitorId, int sessionId)
{
  //some type of validation
  var session = await GetValidSession(accountId, siteId, visitorId, sessionId);
  BackgroundTaskManager.Run(() => DataAdapter.UpdateSessionCheckAsync(sessionId, DateTime.UtcNow));

  return new Session
  {
    Id = sessionId,
    VisitorId = session.VisitorId
  };
}

Note that this is assuming UpdateSessionCheckAsync does return a Task.

In particular:

  • async void methods have very awkward error handling semantics. If there is some problem writing to the database, an async void method will crash your application by default.
  • Task.Factory.StartNew is rather dangerous, as I explain on my blog.
Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • Thank you Stephen. those blog post are exactly what I needed. – Mike_G Apr 03 '14 at 15:58
  • @Vlado: The links work fine for me. Do you mean the code on my blog doesn't work? – Stephen Cleary Jul 04 '15 at 12:52
  • What is the problem with this async void? I just bumped into the same issue: the whole web application dies if there was an exception, just changing it to async Task fixed the problem completely, I do not really grasp why async void should be different from async Task... – Ilya Chernomordik Oct 22 '15 at 13:22
  • @IlyaChernomordik: Think of it from the concept of the `async` method. If it completes with an exception, and the method returns a `Task`, then that exception can just be placed on the `Task`. What can it do if it completes with an exception, but the method returns `void`? The only real options are 1) silently ignore the exception (a horrible idea), or 2) raise the exception as though it were a "top-level" exception for that context. `async void` does (2). – Stephen Cleary Oct 22 '15 at 16:40
  • Well, the async/await are meant to be transparent kind of, right? If you have ordinary action with void, then the exception will be properly handled. So I would think it makes sense to maintain this behavior, otherwise it seems that async void is quite useless, so we should always make async Task instead and in this case it could be done implicitly. – Ilya Chernomordik Oct 23 '15 at 07:20
  • @IlyaChernomordik: I encourage you to post your own questions on SO. To answer your latest: what you mean by "properly handled" is "travels up the call stack", and this is impossible to achieve with an `async void` method; it can only be done with an `async Task` method. `async void` is certainly *unnatural*; it was only added to the language to allow asynchronous event handlers without incurring massive breaking changes across the entire .NET ecosystem. – Stephen Cleary Oct 23 '15 at 13:10