ASP.NET 4.6 asynchronous controller method lost HttpContext.Current after waiting

I have an ASP.NET application targeting .NET 4.6, and am losing my mind trying to understand why HttpContext.Current becomes null after the first wait inside my async MVC controller action.

I checked and triple-checked that my project is targeting v4.6, and that the web.config attribute of targetFramework also 4.6.

SynchronizationContext.Current is assigned both before and after the wait, and it is correct, i.e. AspNetSynchronizationContext , not the former.

FWIW, the expected question switches the threads to continue, which is probably due to the fact that it calls an external input / output code (calling an asynchronous database), but this should not be a problem, AFAIU.

And yet, it is! The fact that HttpContext.Current getting null causes a number of problems for my code in line, and that makes no sense to me.

I checked the usual recommendations , and I am sure that I am doing everything I should. I also have absolutely no ConfigureAwait in my code!

What I have are several async event handlers on my HttpApplication instance:

 public MvcApplication() { var helper = new EventHandlerTaskAsyncHelper(Application_PreRequestHandlerExecuteAsync); AddOnPreRequestHandlerExecuteAsync(helper.BeginEventHandler, helper.EndEventHandler); helper = new EventHandlerTaskAsyncHelper(Application_PostRequestHandlerExecuteAsync); AddOnPostRequestHandlerExecuteAsync(helper.BeginEventHandler, helper.EndEventHandler); } 

I need these two because of user authorization and cleanup logic, which requires asynchronous. AFAIU, this is supported and should not be a problem.

What else could be causing this puzzling behavior that I see?

UPDATE: additional observation.

The SynchronizationContext link remains unchanged after waiting before and before waiting. But his internal changes change between them, as can be seen in the screenshots below!

BEFORE YOU WAIT: Before entering

AFTER: After continuing

I am not sure how (or even if) this may be relevant to my problem at the moment. Hope someone else can see it!

+7
c # async-await
source share
1 answer

I decided to set the clock on HttpContext.Current and started pacing in β€œwait” to see exactly where it was changing. Not surprisingly, the thread changed several times as I continued, which made sense to me because there were a lot of real asynchronous calls on the way. All of them saved an instance of HttpContext.Current , as expected.

And then I got into the line of violation ...

 var observer = new EventObserver(); using (EventMonitor.Instance.Observe(observer, ...)) { await plan.ExecuteAsync(...); } var events = await observer.Task; // Doh! 

A brief explanation is that plan.ExecuteAsync performs a series of steps that are reported to a specialized event log in a non-blocking manner through a dedicated thread. This is commercial software, the nature of reporting events is quite widely used throughout the code. In most cases, these events are not directly related to the caller. But one or two places are special in that the caller would like to know what events occurred as a result of the execution of certain code. This is when an instance of EventObserver , as shown above.

await observer.Task needed to wait for all relevant events to be processed and respected. The task in question comes from an instance of TaskCompletionSource owned by the observer. After all the events have leaked, the SetResult source SetResult called from the thread that processed the events. My initial implementation of this detail was - naively - as follows:

 public class EventObserver : IObserver<T> { private readonly ObservedEvents _events = new ObservedEvents(); private readonly TaskCompletionSource<T> _source; private readonly SynchronizationContext _capturedContext; public EventObserver() { _source = new TaskCompletionSource<T>(); // Capture the current synchronization context. _capturedContext = SynchronizationContext.Current; } void OnCompleted() { // Apply the captured synchronization context. SynchronizationContext.SetSynchronizationContext(_capturedContext); _source.SetResult(...); } } 

Now I can see that calling SetSynchronizationContext before SetResult does not do what I was hoping it would be. The goal was to apply the original synchronization context to the continuation of the await observer.Task .

Now the question is: how do I do it right? I assume it will take an explicit call to ContinueWith somewhere.

UPDATE

Here is what I did. I passed the parameter TaskCreationOptions.RunContinuationsAsynchronously TaskCompletionSource ctor and changed the Task property in my EventObserver class to enable an explicitly synchronized continuation:

 public Task<T> Task { get { return _source.Task.ContinueWith(t => { if (_capturedContext != null) { SynchronizationContext.SetSynchronizationContext(_capturedContext); } return t.Result; }); } } 

So, now that the code calls await observer.Task , the continuation will ensure that the correct context is entered first. So far this is working correctly!

0
source

All Articles