Async call to the WCF service doesn't preserve CurrentCulture

爱⌒轻易说出口 提交于 2019-11-28 00:11:12

Resetting culture back to what it was before the await is usually the job of the synchronization context. But since (as far as I understand it), you don't have any synchronization context, the await won't modify the thread culture in any way.

This means that if you're back on the same thread after await, you're going to see the culture that you set. But if you resume on a different thread, you will see the default culture (unless somebody else modified it for that thread too).

When are you going to be on the same thread after an await, when there is no synchronization context? You're guaranteed to be on the same thread if the async operation completes synchronously (like your GetIntAsync()), because in that case, the method just synchronously continues after the await. You can also resume on the same thread if you're lucky, but you can't rely on that. This can be non-deterministic, so your code might seem to work sometimes, and sometimes not.

What you should do when you want to flow culture with awaits and you don't have a synchronization context that does it for you? Basically, you have two options:

  1. Use a custom awaiter (see Stephen Toub's WithCulture(), as linked by Noseratio). This means that you need to add this to all your awaits where you need to flow the culture, which could be cumbersome.
  2. Use a custom synchronization context that will flow the culture automatically for every await. This means you can set up the context only once for each operation and it will work correctly (similarly to what ASP.NET synchronization context does). This is probably the better solution in the long run.

I don't have a definitive answer on why the described behavior may take place, especially given the statement that the whole chain of calls stay on the same thread (ManagedThreadId remains the same). Moreover, my assumption that culture doesn't flow with the execution context under AspNetSynchronizationContext was wrong, it does flow, in fact. I took the point from @StephenCleary's comment about trying await Task.Delay and verified that with the following little research:

// GET api/values/5
public async Task<string> Get(int id)
{
    // my default culture is en-US
    Log("Get, enter");

    Thread.CurrentThread.CurrentCulture = new CultureInfo("hu-HU");

    Log("Get, before Task.Delay");
    await Task.Delay(200);
    Thread.Sleep(200);

    Log("Get, before Task.Run");
    await Task.Run(() => Thread.Sleep(100));
    Thread.Sleep(200);

    Log("Get, before Task.Yield");
    await Task.Yield();

    Log("Get, before exit");
    return "value";
}

static void Log(string message)
{
    var ctx = SynchronizationContext.Current;
    Debug.Print("{0}; thread: {1}, context: {2}, culture {3}",
        message,
        Thread.CurrentThread.ManagedThreadId,
        ctx != null ? ctx.GetType().Name : String.Empty,
        Thread.CurrentThread.CurrentCulture.Name);
}

Output:

Get, enter; thread: 12, context: AspNetSynchronizationContext, culture en-US
Get, before Task.Delay; thread: 12, context: AspNetSynchronizationContext, culture hu-HU
Get, before Task.Run; thread: 11, context: AspNetSynchronizationContext, culture hu-HU
Get, before Task.Yield; thread: 10, context: AspNetSynchronizationContext, culture hu-HU
Get, before exit; thread: 11, context: AspNetSynchronizationContext, culture hu-HU

Thus, I could only imagine something inside wcfClient.GetResultAsync() actually changes the current thread's culture. A workaround for this could be to use a customer awaiter like Stephen Toub's CultureAwaiter. However, this symptom is worrying. Maybe you should search the generated WCF client proxy code for "culture" and check what's going on in there. Try stepping it through and find out at what point Thread.CurrentThread.CurrentCulture gets reset.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!