对WCF服务的异步调用不会保留CurrentCulture

根据这个问题的答案async/await call应该保留CurrentCulture。 就我而言,当我调用自己的异步方法时,将保留CurrentCulture。 但是,如果我调用某种WCF服务方法,则不会保留CurrentCulture,而是将其更改为看起来像服务器默认线程文化的东西。

我查看了调用托管线程的内容。 它发生在每个代码行都在一个托管线程上执行(ManagedThreadId保持不变)。 在调用我自己的异步方法后,CultureInfo保持不变。 但是当我调用WCF的方法时,ManagedTrheadId保持不变,但CurrentCulture已更改。

简化的代码如下所示::

 private async Task Test() { // Befoe this code Thread.CurrentThread.CurrentCulture is "ru-RU", ie the default server's culture // Here we change current thread's culture to some desired culture, eg hu-HU Thread.CurrentThread.CurrentCulture = new CultureInfo("hu-HU"); var intres = await GetIntAsync(); // after with await Thread.CurrentThread.CurrentCulture is still "hu-HU" var wcfres = await wcfClient.GetResultAsync(); // And here Thread.CurrentThread.CurrentCulture is "ru-RU", ie the default server's culture } private async Task GetIntAsync() { return await Task.FromResult(1); } 

wcfClient是自动生成的WCF客户端的实例( System.ServiceModel.ClientBaseinheritance者)

这一切都发生在ASP.NET MVC Web API(自托管)中,我使用Accept-Language标头将CurrentCulture设置为稍后访问它并使用它来返回本地化资源。 我可以在没有CurrentCulture的情况下继续前进,只是将CultureInfo传递给每个方法,但我不喜欢这种方法。

为什么CurrentThread的CurrentCulture在调用WCF服务后被更改,并且在调用我自己的异步方法后保持不变? 可以“修复”吗?

将文化重置为await之前的文化通常是同步上下文的工作。 但是因为(据我所知),你没有任何同步上下文, await不会以任何方式修改线程文化。

这意味着如果您在await之后回到相同的线程,您将看到您设置的文化。 但是如果你在另一个线程上恢复,你将看到默认文化(除非其他人为该线程修改了它)。

await ,当没有同步上下文时,您何时在同一个线程上? 如果异步操作同步完成(例如GetIntAsync() ),则保证在同一线程上,因为在这种情况下,该方法在await之后同步继续。 如果幸运的话,你也可以在同一个post上继续,但你不能依赖它。 这可能是不确定的,因此您的代码有时似乎有效,有时则无效。

当您想要await s并且没有为您执行此操作的同步上下文时,您应该做什么? 基本上,您有两种选择:

  1. 使用自定义awaiter(参见由Noseratio链接的Stephen Toub的WithCulture() )。 这意味着您需要将此添加到您需要流动文化的所有await中,这可能很麻烦。
  2. 使用自定义同步上下文,该上下文将自动为每个await流动文化。 这意味着您只能为每个操作设置一次上下文,它将正常工作(类似于ASP.NET同步上下文所做的)。 从长远来看,这可能是更好的解决方案。

我没有明确回答为什么会出现所描述的行为,特别是考虑到整个调用链保持在同一个线程上的声明( ManagedThreadId保持不变)。 此外, 我假设文化不会与AspNetSynchronizationContext下的执行上下文一起AspNetSynchronizationContext是错误的,事实上它确实是流动的 。 我从@ await Task.Delay关于尝试await Task.Delay的评论中得到了一点,并通过以下一些小研究证实了这一点:

 // GET api/values/5 public async Task 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); } 

输出:

得到,输入;  thread:12,context:AspNetSynchronizationContext,culture en-US
在Task.Delay之前获取;  thread:12,context:AspNetSynchronizationContext,culture hu-HU
在Task.Run之前获取;  thread:11,context:AspNetSynchronizationContext,culture hu-HU
在Task.Yield之前获取;  thread:10,context:AspNetSynchronizationContext,culture hu-HU
退出之前获取;  thread:11,context:AspNetSynchronizationContext,culture hu-HU

因此,我只能想象wcfClient.GetResultAsync()内部的东西实际上改变了当前线程的文化。 解决这个问题的方法可能是使用像Stephen Toub的 CultureAwaiter这样的客户等待者。 然而,这种症状令人担忧。 也许您应该在生成的WCF客户端代理代码中搜索“culture”并检查其中发生了什么。 尝试单步执行并找出Thread.CurrentThread.CurrentCulture在什么时候重置。