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

Posted

技术标签:

【中文标题】对 WCF 服务的异步调用不会保留 CurrentCulture【英文标题】:Async call to the WCF service doesn't preserve CurrentCulture 【发布时间】:2013-11-19 08:55:26 【问题描述】:

根据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 因此这是一个自托管的 ASP.NET MVC Web API (.net 4.5),它基于 WCF Web API(请更正如果我错了,我)。 @DmitryLobanov,正确,自托管 Web API 运行时没有 AspNetSynchronizationContext。请参阅斯蒂芬对我的回答的 cmets。 【参考方案1】:

将文化重置为await 之前的状态通常是同步上下文的工作。但是由于(据我了解),您没有任何同步上下文,await 不会以任何方式修改线程文化。

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

在没有同步上下文的情况下,await 之后你什么时候会在同一个线程上?如果异步操作同步完成(如您的GetIntAsync()),则保证您在同一个线程上,因为在这种情况下,该方法只是在await 之后同步继续。如果你幸运的话,你也可以在同一个线程上继续,但你不能依赖它。这可能是非确定性的,因此您的代码有时似乎可以正常工作,但有时却不行。

如果您想使用awaits 进行文化流动,但您没有同步上下文,您应该怎么做?基本上,您有两种选择:

    使用自定义等待器(参见Stephen Toub's WithCulture(),由 Noseratio 链接)。这意味着您需要将其添加到您需要传播文化的所有 awaits 中,这可能很麻烦。 使用自定义同步上下文,该上下文将为每个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 回调方法永远不会执行

中间层异步 WCF 服务调用和向 UI 层返回数据的质量设计

异步 WCF 服务超时

WCF 暂停调用