Async WebApi Thread.CurrentCulture

Dan*_*ele 40 c# culture multithreading asynchronous asp.net-web-api

我有一个自托管的OWIN托管Web API项目,为我提供了一些基本的REST方法.

我想要多语言错误消息,所以我使用资源文件和BaseControllerThread.CurrentCultureThread.CurrentUICulture设置为请求的Accept-Language标头.

public override Task<HttpResponseMessage> ExecuteAsync(HttpControllerContext controllerContext, CancellationToken cancellationToken)
{
    if (controllerContext.Request.Headers.AcceptLanguage != null && 
        controllerContext.Request.Headers.AcceptLanguage.Count > 0)
    {
        string language = controllerContext.Request.Headers.AcceptLanguage.First().Value;
        var culture = CultureInfo.CreateSpecificCulture(language);

        Thread.CurrentThread.CurrentCulture = culture;
        Thread.CurrentThread.CurrentUICulture = culture;
    }

    base.ExecuteAsync(controllerContext, cancellationToken);
}
Run Code Online (Sandbox Code Playgroud)

这一切都很好,但如果我使我的控制器方法异步,问题就出现了.

当我在方法中使用await时,它可能会在另一个线程中继续,因此我的CurrentCultureCurrentUICulture会丢失.

这是我用来找到这个问题的一个小例子.

public async Task<HttpResponseMessage> PostData(MyData data)
{
    Thread currentThread = Thread.CurrentThread;

    await SomeThing();

    if (Thread.CurrentThread.CurrentCulture != currentThread.CurrentCulture)
        Debugger.Break();
}
Run Code Online (Sandbox Code Playgroud)

我并不总是打破Debugger.Break行,但大部分时间我都这样做.

这是我实际使用资源文件的示例.

public async Task<HttpResponseMessage> PostMyData(MyData data)
{
    //Before this if I'm in the correct thread and have the correct cultures
    if (await this._myDataValidator.Validate(data) == false)
        //However, I might be in another thread here, so I have the wrong cultures
        throw new InvalidMyDataException(); 
}

public class InvalidMyDataException : Exception
{
    public InvalidMyDataException()
        //Here I access my resource file and want to get the error message depending on the current culture, which might be wrong
        : base(ExceptionMessages.InvalidMyData) 
    {

    }
}
Run Code Online (Sandbox Code Playgroud)

一些额外的信息:我有一大堆这样的例外,它们都被一个自定义的ExceptionFilterAttribute捕获,然后创建响应.

因此,在使用之前始终设置文化将是很多代码.

Ste*_*ary 25

正如Joe指出的那样,文化是由HttpContextASP.NET中传递的.ASP.NET执行此操作的方式是SynchronizationContext在请求启动时安装,并且该上下文也用于恢复异步方法(默认情况下).

因此,有两种方法可以解决这个问题:您可以编写自己的方法来SynchronizationContext默认保留文化,也可以明确地保留每种文化await.

为了保护每个文化await,您可以使用Stephen Toub的代码:

public static CultureAwaiter WithCulture(this Task task) 
{ 
    return new CultureAwaiter(task); 
}

public class CultureAwaiter : INotifyCompletion
{ 
    private readonly TaskAwaiter m_awaiter; 
    private CultureInfo m_culture;

    public CultureAwaiter(Task task) 
    { 
        if (task == null) throw new ArgumentNullException("task"); 
        m_awaiter = task.GetAwaiter(); 
    }

    public CultureAwaiter GetAwaiter() { return this; }

    public bool IsCompleted { get { return m_awaiter.IsCompleted; } }

    public void OnCompleted(Action continuation) 
    { 
        m_culture = Thread.CurrentThread.CurentCulture; 
        m_awaiter.OnCompleted(continuation); 
    }

    public void GetResult() 
    { 
        Thread.CurrentThread.CurrentCulture = m_culture; 
        m_awaiter.GetResult(); 
    } 
}
Run Code Online (Sandbox Code Playgroud)

这种SynchronizationContext方法比较复杂,但一旦设置好,它就会更容易使用.我不知道类似ASP.NET的上下文的一个很好的例子,但一个很好的起点是我的MSDN文章.


Mik*_*chs 7

从.NET 4.5开始,要为所有线程设置默认区域性,请使用:

CultureInfo.DefaultThreadCurrentCulture = culture;
CultureInfo.DefaultThreadCurrentUICulture = culture;
Run Code Online (Sandbox Code Playgroud)

  • 在多用户环境中为所有线程设置*default culture*失败,用户可以选择**不同的文化**. (5认同)