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

时间:2013-11-19 08:55:27

标签: c# wcf asp.net-web-api async-await cultureinfo

根据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服务后被更改,并在调用我自己的异步方法后保持不变?可以“修复”吗?

2 个答案:

答案 0 :(得分:2)

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

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

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

如果您希望使用await流动文化并且没有为您执行此操作的同步上下文,您应该怎么做?基本上,您有两种选择:

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

答案 1 :(得分:1)

我没有明确回答为什么会出现所描述的行为,特别是考虑到整个调用链保持在同一个线程(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);
}

<强>输出:

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

因此,我只能想象wcfClient.GetResultAsync()内的某些内容实际上会改变当前线程的文化。解决方法可能是使用Stephen Toub's CultureAwaiter之类的客户等待。然而,这种症状令人担忧。也许您应该搜索生成的WCF客户端代理代码以查找“文化”并检查其中发生了什么。尝试单步执行,找出Thread.CurrentThread.CurrentCulture重置的位置。