3

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?

Community
  • 1
  • 1
user247702
  • 23,641
  • 15
  • 110
  • 157
  • Are you sure you need `ContinueWith` and `TaskContinuationOptions.AttachedToParent` here? Check [this](http://blogs.msdn.com/b/pfxteam/archive/2012/09/22/new-taskcreationoptions-and-taskcontinuationoptions-in-net-4-5.aspx) to make sure `TaskContinuationOptions.AttachedToParent` gives you the expected behavior. – noseratio Apr 22 '14 at 09:16
  • @Noseratio I believe so, yes. See [Why does this async code sometimes fail, and only when not observed?](http://stackoverflow.com/questions/19175885/why-does-this-async-code-sometimes-fail-and-only-when-not-observed). Feel free to post a better solution if mine is not correct. – user247702 Apr 22 '14 at 09:27
  • @Noseratio Is it better to avoid `ContinueWith` altogether? It starts to feel like I'm digging myself into a hole for no reason. In short, I'm doing a Web API request, deserialise the response and execute a callback. – user247702 Apr 22 '14 at 09:33
  • 1
    Yes, I think you really don't need `ContinueWith` here. Why can you not just use `async/await`? – noseratio Apr 22 '14 at 09:49
  • 1
    @Noseratio This project was our first time making use of `async`, I guess during our attempts we found that `ContinueWith` (seemingly) worked and we ended up keeping that code. I'll try rewriting the calls, thank you for your input. – user247702 Apr 22 '14 at 09:53
  • No problem, I also tried to address the second comment [here](http://stackoverflow.com/a/23216300/1768303). – noseratio Apr 22 '14 at 10:01

2 Answers2

6

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();
    }
Gusdor
  • 14,001
  • 2
  • 52
  • 64
  • The overload of the first snippet isn't correct, but `CancellationToken.None, TaskContinuationOptions.AttachedToParent , TaskScheduler.FromCurrentSynchronizationContext()` compiles and the context is passed. Is `CancellationToken.None` correct? The second snippet isn't quite correct, either. – user247702 Apr 22 '14 at 08:57
  • @Stijn well spotted. That overload always catches me out. was it just the semi-colon in the second snippet? – Gusdor Apr 22 '14 at 09:34
  • In the second snippet, `request` isn't defined and I'm not sure what you meant to type in the snippet. – user247702 Apr 22 '14 at 09:39
  • @Gusdor: Each method captures its own context, so it doesn't matter if `GetAsync` uses `ConfigureAwait(false)`; `await` will Do The Right Thing even if it does. There's no need to use `ConfigureAwait(true)`. – Stephen Cleary Apr 22 '14 at 12:19
  • @StephenCleary i must be mixed up. Funnily enough, I sourced my understanding of configure await from your blog! http://blog.stephencleary.com/2012/02/async-and-await.html – Gusdor Apr 22 '14 at 12:44
  • 1
    @Gusdor: Sorry about that. I attempted to explain it in the `each "level" of async method calls has its own context` part of that post. It is a bit hard to explain, though. – Stephen Cleary Apr 22 '14 at 13:07
  • @StephenCleary not a problem. A re-read has made it very clear. – Gusdor Apr 22 '14 at 13:19
-1

Did you try with TaskContinuationOptions.ExecuteSynchronously? It should ran the continuation task in the same thread...

http://msdn.microsoft.com/en-us/library/vstudio/system.threading.tasks.taskcontinuationoptions

"Specifies that the continuation task should be executed synchronously. With this option specified, the continuation will be run on the same thread that causes the antecedent task to transition into its final state. If the antecedent is already complete when the continuation is created, the continuation will run on the thread creating the continuation. Only very short-running continuations should be executed synchronously."

Bidou
  • 7,378
  • 9
  • 47
  • 70
  • That didn't help, I've tried both `TaskContinuationOptions.AttachedToParent | TaskContinuationOptions.ExecuteSynchronously` and `TaskContinuationOptions.ExecuteSynchronously`. – user247702 Apr 22 '14 at 08:34