3

I read a lot of codes trying to use Task.Run without success.

What I want to achive:

  • In an ASP.NET WebForm event (click event handler) call a Fire & Forget method (not block the current flow of execution).

What I tried and don't understant why it's not working:


First Version:
protected void btn_Click(object sender, EventArgs e)
{
    // Some actions
    // Should insert a record in database --> OK

    //Tried this call with and without ConfigureAwait(false)
    Task.Run(() => MyClass.doWork()).ConfigureAwait(false);

    // Should insert a record in database --> OK
    // Some actions not blocked by the previous call
}

public static class MyClass
{
    public static void doWork()
    {
        // Should insert a record in database --> NOT INSERTED
    }
}


Second Version:
protected void btn_Click(object sender, EventArgs e)
{
    // Some actions
    // Should insert a record in database --> OK

    Bridge.call_doWork();

    // Should insert a record in database --> OK
    // Some actions not blocked by the previous call
}

public static class Bridge
{
    public static async Task call_doWork()
    {
        //Tried this call with and without ConfigureAwait(false)
        await Task.Run(() => MyClass.doWork()).ConfigureAwait(false);
    }
}

public static class MyClass
{
    public static void doWork()
    {
        // Should insert a record in database --> NOT INSERTED
    }
}

So I call the Fire & Forget method, which should insert a record in the database, but there's no record inserted.

The inserts before and after the call to the Fire & Forget method are done.

I don't know how to resolve my issue.

krlzlx
  • 5,752
  • 14
  • 47
  • 55
  • When you put a breakpoint in doWork(), does it hit the breakpoint? – Gabriel Luci May 03 '16 at 14:43
  • Have you confirmed `MyClass.doWork()` works synchronously? – weston May 03 '16 at 14:44
  • I'm sorry, I can't use the debugger, as I test the code on a server and using assemblyBinding in web.config. – krlzlx May 03 '16 at 14:45
  • 2
    Scott Hanselman did a roundup of [How to run Background Tasks in ASp.Net](http://www.hanselman.com/blog/HowToRunBackgroundTasksInASPNET.aspx) a couple of years back, and not much has changed. – Damien_The_Unbeliever May 03 '16 at 14:47
  • Just tried; MyClass.doWork() works synchronously. – krlzlx May 03 '16 at 14:48
  • @Damien_The_Unbeliever I tried with `HostingEnvironment.QueueBackgroundWorkItem` and the result is the same as with `Task.Run` – krlzlx May 03 '16 at 14:56
  • 3
    Does doWork() use anything that is only available on the main thread (like using HttpContext)? – Gabriel Luci May 03 '16 at 15:06
  • Yes! I use HttpContext to get the Session. But I can't change that without changing a lot of code...Why didn't I get an EventViewer event with the HttpContext call? I got one when using `HostingEnvironment.QueueBackgroundWorkItem` instead of `Task.Run`. – krlzlx May 03 '16 at 15:09
  • I'm not sure. But HttpContext.Current will be null in any other thread but the main thread. – Gabriel Luci May 03 '16 at 15:13
  • 1
    By design than - providing [MCVE] is good practice for SO questions by the way and post have no indication of using HttpContext (which would be immediately clear to you too as you expect request context to be available after request is complete)... I'd recommend also reading http://stackoverflow.com/questions/13489065/best-practice-to-call-configureawait-for-all-server-side-code to avoid `....ConfigureAwait(false);` . – Alexei Levenkov May 03 '16 at 15:14
  • @Alexei Levenkov: You're absolutely right. I thought the method was not the problem as it worked synchronously and had no errors in the EventViewer. – krlzlx May 03 '16 at 15:18
  • @AlexeiLevenkov In my own testing, I found that when you're doing a fire & forget from ASP.NET, you need ConfigureAwait(false). Otherwise, the client does not receive the response until that Task is complete (presumably, because it cannot release the context until that task is done, because it needs to resume on the main context), which defeats the purpose of fire & forget. – Gabriel Luci May 03 '16 at 15:23
  • @GabrielLuci indeed - you can't have it both ways - request completion kills the context/session state... Using `ConfigureAwait(false)` explicitly says that you don't need HttpContext, Session, CultureInfo.CurrentCulture and hence don't need to block request completion. – Alexei Levenkov May 03 '16 at 15:52

2 Answers2

3

HttpContext will not be available in threads other than the main thread, so you can't depend on it.

But you can pass data from HttpContext to your method when you start the task. For example:

Task.Run(() => MyClass.doWork(HttpContext.Current.Session["somedata"])).ConfigureAwait(false);
Gabriel Luci
  • 38,328
  • 4
  • 55
  • 84
2

Why didn't I get an EventViewer event with the HttpContext call? I got one when using HostingEnvironment.QueueBackgroundWorkItem instead of Task.Run.

OK, first off, if you have QueueBackgroundWorkItem available, why would you ever use Task.Run for fire-and-forget work?

As I describe on my blog, using Task.Run for fire-and-forget on ASP.NET is a really bad idea! QueueBackgroundWorkItem is the minimum viable solution, and that's only if you accept unreliability.

QueueBackgroundWorkItem does a couple of things for you beyond Task.Run: it registers the work with the ASP.NET runtime (which minimizes but does not eliminate the possibility that the work will not complete), and it catches exceptions and logs them for you (which is why you were seeing the event notifications).

So, you were seeing an event for your exception because QBWI was doing that for you. Whereas with the Task.Run code, the exception would be caught and placed on the returned Task (as it should be), and then your code completely ignored that task, thus silently swallowing the exception.

Yes! I use HttpContext to get the Session. But I can't change that without changing a lot of code.

As others have noted, HttpContext is only valid within a request context. So, when you explicitly run background code, of course it doesn't have a request context. Background code must be independent of requests, by definition.


But there's one other really important consideration that I think you're overlooking:

public static void doWork()
{
    // Should insert a record in database --> NOT INSERTED
}

Are you sure that your app is perfectly OK if doWork once in a while doesn't execute? Because that's what happens to background tasks. ASP.NET was designed to respond to requests, not run background tasks.

So, every once in a blue moon, the record that should have been inserted by doWork won't show up. If this is unacceptable, then you shouldn't be doing fire-and-forget.

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