ContinueWith loses the SynchronizationContext

asp.net, asp.net-mvc, async-await, c#, c#-5.0

Solution

In order to force scheduling of the continuation on the request context thread, you need to specify the `TaskScheduler` that should be used when scheduling the continuation.

public async Task<ActionResult> Index()
{
    Log("before GetAsync");
    await new HttpClient().GetAsync("http://www.example.com/")
        .ContinueWith(request =>
        {
            Log("ContinueWith");
            request.Result.EnsureSuccessStatusCode();
        }, 
        TaskContinuationOptions.AttachedToParent,
        CancellationToken.None,
        TaskScheduler.FromCurrentSynchronizationContext());

    return View();
}

Howver, you are using `await` which automatically marshals continuations on to the current `SynchronizationContext`. You should be able to do this:

public async Task<ActionResult> Index()
    {
        Log("before GetAsync");
        HttpResponseMessage request = await new HttpClient().GetAsync("http://www.example.com/");

        //everything below here is you 'continuation' on the request context
        Log("ContinueWith");
        request.EnsureSuccessStatusCode();

        return View();
    }

Problem

In the snippet below, the `SynchronizationContext` is lost, and because of that also the `CurrentCulture` and `CurrentUICulture`. `Log()` comes from this answer. ``` public async Task<ActionResult> Index() { Log("before GetAsync"); await new HttpClient().GetAsync("http://www.example.com/") .ContinueWith(request => { Log("ContinueWith"); request.Result.EnsureSuccessStatusCode(); }, TaskContinuationOptions.AttachedToParent); return View(); } static void Log(string message) { var ctx = System.Threading.SynchronizationContext.Current; System.Diagnostics.Debug.Print("{0}; thread: {1}, context: {2}, culture: {3}, uiculture: {4}", message, System.Threading.Thread.CurrentThread.ManagedThreadId, ctx != null ? ctx.GetType().Name : String.Empty, System.Threading.Thread.CurrentThread.CurrentCulture.Name, System.Threading.Thread.CurrentThread.CurrentUICulture.Name); } ``` This is the output: before GetAsync; thread: 56, context: AspNetSynchronizationContext, culture: nl, uiculture: nl ContinueWith; thread: 46, context: , culture: nl-BE, uiculture: en-US Before the `GetAsync`, the culture and UI culture have the values I set in `Application_BeginRequest`. Inside the `ContinueWith`, the context is missing, the culture is set to what's provided by the browser, and UI culture is set to some default. From what I understand, everything with `AspNetSynchronizationContext` should be happening automatically. What's wrong with my code?

Original source