对 WCF 服务的异步调用不会保留 CurrentCulture
Posted
技术标签:
【中文标题】对 WCF 服务的异步调用不会保留 CurrentCulture【英文标题】:Async call to the WCF service doesn't preserve CurrentCulture 【发布时间】:2013-12-02 17:46:18 【问题描述】:根据this question async/await
调用中的答案应该保留CurrentCulture。就我而言,当我调用自己的异步方法时,CurrentCulture 会被保留。但是,如果我调用 WCF 服务的某些方法,则不会保留 CurrentCulture,而是将其更改为类似于服务器默认线程文化的东西。
我查看了调用了哪些托管线程。发生这种情况时,每一行代码都在单个托管线程上执行(ManagedThreadId 保持不变)。调用我自己的异步方法后,CultureInfo 保持不变。但是当我调用 WCF 的方法时,ManagedTrheadId 保持不变,但 CurrentCulture 发生了变化。
简化后的代码如下::
private async Task Test()
// Befoe this code Thread.CurrentThread.CurrentCulture is "ru-RU", i.e. the default server's culture
// Here we change current thread's culture to some desired culture, e.g. 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", i.e. the default server's culture
private async Task<int> GetIntAsync()
return await Task.FromResult(1);
wcfClient
是自动生成的 WCF 客户端的一个实例(System.ServiceModel.ClientBase
的继承者)
这一切都发生在 ASP.NET MVC Web API(自托管)中,我使用 Accept-Language 标头设置 CurrentCulture 以便稍后访问它并使用它返回本地化资源。我可以在没有 CurrentCulture 的情况下继续前进,只需将 CultureInfo 传递给每个方法,但我不喜欢这种方法。
为什么 CurrentThread 的 CurrentCulture 在调用 WCF 服务后会发生变化,而在调用我自己的异步方法后保持不变?可以“修复”吗?
【问题讨论】:
当您将GetIntAsync
更改为:await Task.Delay(100); return 1;
时,您会看到什么?
有趣的是,当您的代码在await
之后继续在不同的线程上时,似乎文化不会在AspNetSynchronizationContext
下流动。您可以从这里与 Stephen Toub 的 CultureAwaiter
等客户服务员一起解决这个问题:blogs.msdn.com/b/pfxteam/archive/2011/01/13/10115642.aspx
当有线程切换时,文化确实在AspNetSynchronizationContext
下流动,verified。所以它必须是 wcfClient.GetResultAsync()
内部的东西。
@Noseratio 我不确定我们是否正在处理 AspNetSynchronizationContext 因此这是一个基于 WCF Web API 的自托管 ASP.NET MVC Web API (.net 4.5)(请更正如果我错了,我)。
@DmitryLobanov,正确,自托管 Web API 运行时没有 AspNetSynchronizationContext
。请参阅斯蒂芬对我的回答的 cmets。
【参考方案1】:
将文化重置回await
之前的状态通常是同步上下文的工作。但是由于(据我了解),您没有任何同步上下文,await
不会以任何方式修改线程文化。
这意味着如果您在await
之后回到同一个线程,您将看到您设置的文化。但是如果你在不同的线程上继续,你会看到默认的文化(除非其他人也为那个线程修改了它)。
在没有同步上下文的情况下,await
之后你什么时候会在同一个线程上?如果异步操作同步完成(如您的GetIntAsync()
),则保证您在同一个线程上,因为在这种情况下,该方法只是在await
之后同步继续。如果你幸运的话,你也可以在同一个线程上继续,但你不能依赖它。这可能是不确定的,因此您的代码有时似乎可以工作,但有时却不行。
如果您想使用await
s 进行文化交流,但您没有同步上下文来为您做这件事,您应该怎么做?基本上,您有两种选择:
-
使用自定义等待器(参见 Stephen Toub's
WithCulture()
,由 Noseratio 链接)。这意味着您需要将其添加到您需要传播文化的所有 await
s 中,这可能很麻烦。
使用自定义同步上下文,该上下文将为每个 await
自动流动区域性。这意味着您可以为每个操作只设置一次上下文,它将正常工作(类似于 ASP.NET 同步上下文所做的)。从长远来看,这可能是更好的解决方案。
【讨论】:
顺便说一句,我相信我在研究这个问题时发现了a bug in culture handling of the ASP.NET synchronization context(认为研究结果是不必要的)。【参考方案2】:对于为什么会发生所描述的行为,我没有明确的答案,特别是考虑到整个调用链保持在同一个线程上的声明(ManagedThreadId
保持不变)。此外,我认为文化不会随着 AspNetSynchronizationContext
下的执行上下文流动的假设是错误的,事实上它确实流动。我从@StephenCleary 关于尝试await Task.Delay
的评论中理解了这一点,并通过以下小研究验证了这一点:
// 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);
输出:
得到,进入;线程:12,上下文:AspNetSynchronizationContext,文化 en-US 获取,在Task.Delay之前;线程:12,上下文:AspNetSynchronizationContext,文化hu-HU 获取,在 Task.Run 之前;线程:11,上下文:AspNetSynchronizationContext,文化hu-HU 在 Task.Yield 之前获取;线程:10,上下文:AspNetSynchronizationContext,文化hu-HU 获取,退出前;线程:11,上下文:AspNetSynchronizationContext,文化hu-HU因此,我只能想象wcfClient.GetResultAsync()
内部的某些东西实际上改变了当前线程的文化。一种解决方法是使用像 Stephen Toub's CultureAwaiter
这样的客户等待者。然而,这种症状令人担忧。也许您应该在生成的 WCF 客户端代理代码中搜索“文化”并检查其中发生了什么。尝试单步执行并找出Thread.CurrentThread.CurrentCulture
在什么时候被重置。
【讨论】:
该操作是自托管的,因此他没有AspNetSynchronizationContext
。所以,没有什么应该明确地保留这种文化,但另一方面,我真的很惊讶 WCF 客户端代理正在改变它。
@StephenCleary,很好,我错过了关于自托管的部分。以上是关于对 WCF 服务的异步调用不会保留 CurrentCulture的主要内容,如果未能解决你的问题,请参考以下文章
System.OutOfMemoryException 对 WCF 服务的多个异步调用
WCF REST HttpContext.Current 异步/等待
从不同线程调用时,WCF Duplex 回调方法永远不会执行