Why thread context does not flow to background thread in async/await?

Topics: ASP.NET Web API, General
Jun 10, 2012 at 12:19 AM

Hi,

This is an issue which originally @tugberk brought to my attention in a related scenario.

This sample is Web API controller in VS2012 using .NET 4.5. My problem is the HttpContext.Current is not null in DoWork (which I expected) but is null in DoWaitAsync:

 

       public async Task<IEnumerable<string>> Get()
        {
            await DoWaitAsync();
            DoWork();
            return  new string[] { "value1", "value2" };

        }


        private Task DoWaitAsync()
        {
            return
                Task.Factory.StartNew(
                () => 
                {
                    // null !!
                    var httpCtx = System.Web.HttpContext.Current;
                    Thread.Sleep(1000);
                });
        }


        public void DoWork()
        {
            //Not null
            var httpCtx = System.Web.HttpContext.Current;
        }

 

My question is why is it null in DoWaitAsync? As for what I have learnt about thread local storage area and context flowing at the time of thread switching, I expect context to flow and all thread local storage data to be copied at the time of context switching.

Is this because async/await turns off context flow (I know that context flow can be turned off) to boost performance?

Jun 10, 2012 at 2:49 AM

It's the Task.Factory.StartNew that switches context. To delay, try this instead:

        public async Task<IEnumerable<string>> Get()
        {
            var httpCtx = System.Web.HttpContext.Current;
            
            await Task.Delay(1000);
            httpCtx = System.Web.HttpContext.Current;
            
            DoWork();
            return new string[] { "value1", "value2" };
        }

That should show a non-null context in all cases.

Henrik

 

Jun 10, 2012 at 10:00 AM

Thanks Henrik. But I think I have not made myself clear. The point is not to create a delay that was just to display an async work is being done.

My point is why context is null inside the async work. As you also said, we have a context switching at the StartNew, so I expect the context to flow to the async work. This example displays what I mean using StartNew:

        public async Task<IEnumerable<string>> Get()
        {

            await Task.Factory.StartNew(() =>
                {
                    Thread.Sleep(1000);
                    var httpCtx = System.Web.HttpContext.Current;
                    Trace.WriteLine(httpCtx == null);
                });
            return new string[] { "value1", "value2" };

        }

Here true is written in the output. So even using StartNew, context has not flowed.

Jun 10, 2012 at 1:54 PM

The issue with StartNew is that it does not by default flow the context -- you have to set it explicitly, The await keyword is special in that it flows it automatically. My example was not to show delay in particular but rather to show using await instead of StartNew. If using the latter you have to specify the synchronization context explicitly, from [1]:

Specifying a Synchronization Context

You can use the TaskScheduler.FromCurrentSynchronizationContext method to specify that a task should be scheduled to run on a particular thread. This is useful in frameworks such as Windows Forms and Windows Presentation Foundation where access to user interface objects is often restricted to code that is running on the same thread on which the UI object was created. For more information, see How to: Schedule Work on a Specified Synchronization Context.

Hope this helps,

Henrik

[1] http://msdn.microsoft.com/en-us/library/dd997402

Jun 10, 2012 at 10:19 PM
Edited Jun 10, 2012 at 10:59 PM

Thanks a lot. OK, so starting a new task does not flow the context unless we use TaskScheduler.... 

As I guessed, it seems this was done to improve performance.

Jun 11, 2012 at 3:08 AM

That's right. By default new tasks are created on any thread pool tread if using the default scheduler. The await keyword explicitly sets the synchronization context.

Henrik