Async WebApi Thread.CurrentCulture

Tengo un proyecto de API web alojado en OWIN alojado en mi propio servidor que me proporciona algunos métodos REST básicos.

Quiero tener mensajes de error multilingües, así que uso los archivos de recursos y un controlador de base que establece Thread.CurrentCulture y Thread.CurrentUICulture en el encabezado Accept-Language de la solicitud.

public override Task 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); } 

Todo funciona bien, pero el problema aparece si hago que mis métodos de control sean asincrónicos .

Cuando uso await en el método, puede continuar en otro hilo, y mi CurrentCulture y CurrentUICulture se pierden.

Aquí hay un pequeño ejemplo que solía encontrar este problema.

 public async Task PostData(MyData data) { Thread currentThread = Thread.CurrentThread; await SomeThing(); if (Thread.CurrentThread.CurrentCulture != currentThread.CurrentCulture) Debugger.Break(); } 

No siempre rompo en la línea Debugger.Break , pero la mayoría de las veces lo hago.

Aquí hay un ejemplo en el que realmente uso mi Archivo de recursos .

 public async Task 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) { } } 

Alguna información adicional: tengo un montón de excepciones como esta, y todas quedan atrapadas en un ExceptionFilterAttribute personalizado que luego crea la respuesta.

Entonces, sería mucho código establecer siempre el cultivo justo antes de usarlo.

Como señaló Joe, la cultura es transferida por HttpContext en ASP.NET. La forma en que ASP.NET hace esto es instalando un SynchronizationContext cuando se inicia una solicitud, y ese contexto también se usa para reanudar los métodos asíncronos (de manera predeterminada).

Entonces, hay un par de formas de abordar el problema: puede escribir su propio SynchronizationContext que preserve la cultura de forma predeterminada, o puede conservar explícitamente la cultura en cada await .

Para preservar la cultura en cada await , puede usar el código de 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(); } } 

El enfoque SynchronizationContext es más complicado, pero una vez que está configurado, será más fácil de usar. No conozco un buen ejemplo de contexto similar a ASP.NET, pero un buen punto de partida es mi artículo de MSDN .

Desde .NET 4.5, para establecer una cultura predeterminada para todos los hilos, use:

 CultureInfo.DefaultThreadCurrentCulture = culture; CultureInfo.DefaultThreadCurrentUICulture = culture; 

Thread.CurrentCulture no se sincroniza entre subprocesos. Sin embargo, tu HttpContext sí. Sería mejor que obtuvieras tu información cultural de tu HttpContext directamente. Puedes hacer algo como

 public override Task 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); HttpContext.Current.Items["Culture"] = culture; //Thread.CurrentThread.CurrentCulture = culture; //Thread.CurrentThread.CurrentUICulture = culture; } base.ExecuteAsync(controllerContext, cancellationToken); } 

y luego, en cualquier tarea necesitas la cultura:

 var culture = HttpContext.Current != null ? HttpContext.Current.Items["Culture"] as CultureInfo : Thread.CurrentThread.CurrentCulture;