Keep CurrentCulture in async/await
Culture does not flow in the .NET Framework, a very notorious problem. It is very hard to solve on Windows, culture is an unmanaged property of a thread so the CLR can't ensure it is always set correctly. That makes tinkering with the CurrentCulture on the main thread a big fat mistake. The bugs you get are very hard to diagnose. Like a SortedList you create on one thread that suddenly isn't sorted anymore on another. Yuck.
Microsoft did something about it in .NET 4.5, they added the CultureInfo.DefaultThreadCurrentCulture property. Also DefaultThreadCurrentUICulture. That still does not guarantee it will be set correctly, unmanaged code you call can change it and the CLR cannot do anything about it. In other words, a bug will be much harder to diagnose. But at least you have some idea when it might change.
UPDATE: this problem was fixed thoroughly in .NET 4.6, culture now flows from one thread to another and the CultureInfo.DefaultThreadCurrentCulture hack is not longer necessary nor useful. Documented in the MSDN article for CultureInfo.CurrentCulture. Details as written right now do not appear to be entirely correct, it always flowed when I tested it and DefaultThreadCurrentCulture appear to play no role at all anymore.
Great explanation is in official doc.
Shortly for .Net 4.6+, you need to set the Culture in the calling thread, before await
.
Thus the Culture will be passed from Current thread to ALL next async/await
(read as subsequent threads).
public async Task SomeFunction(){
Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo(locale);
Thread.CurrentThread.CurrentCulture = Thread.CurrentThread.CurrentUICulture;
await OtherFunction();
}
So far I've created my own SynchronizationContext
, which I've tested with both ASP.NET and console applications, and in both it keeps the culture as I want it:
/// <summary>
/// Class that captures current thread's culture, and is able to reapply it to different one
/// </summary>
internal sealed class ThreadCultureHolder
{
private readonly CultureInfo threadCulture;
private readonly CultureInfo threadUiCulture;
/// <summary>
/// Captures culture from currently running thread
/// </summary>
public ThreadCultureHolder()
{
threadCulture = Thread.CurrentThread.CurrentCulture;
threadUiCulture = Thread.CurrentThread.CurrentUICulture;
}
/// <summary>
/// Applies stored thread culture to current thread
/// </summary>
public void ApplyCulture()
{
Thread.CurrentThread.CurrentCulture = threadCulture;
Thread.CurrentThread.CurrentUICulture = threadUiCulture;
}
public override string ToString()
{
return string.Format("{0}, UI: {1}", threadCulture.Name, threadUiCulture.Name);
}
}
/// <summary>
/// SynchronizationContext that passes around current thread's culture
/// </summary>
internal class CultureAwareSynchronizationContext : SynchronizationContext
{
private readonly ThreadCultureHolder cultureHolder;
private readonly SynchronizationContext synchronizationImplementation;
/// <summary>
/// Creates default SynchronizationContext, using current(previous) SynchronizationContext
/// and captures culture information from currently running thread
/// </summary>
public CultureAwareSynchronizationContext()
: this(Current)
{}
/// <summary>
/// Uses passed SynchronizationContext (or null, in that case creates new empty SynchronizationContext)
/// and captures culture information from currently running thread
/// </summary>
/// <param name="previous"></param>
public CultureAwareSynchronizationContext(SynchronizationContext previous)
: this(new ThreadCultureHolder(), previous)
{
}
internal CultureAwareSynchronizationContext(ThreadCultureHolder currentCultureHolder, SynchronizationContext currentSynchronizationContext)
{
cultureHolder = currentCultureHolder;
synchronizationImplementation = currentSynchronizationContext ?? new SynchronizationContext();
}
public override void Send(SendOrPostCallback d, object state)
{
cultureHolder.ApplyCulture();
synchronizationImplementation.Send(d, state);
}
public override void Post(SendOrPostCallback d, object state)
{
synchronizationImplementation.Post(passedState =>
{
SetSynchronizationContext(this);
cultureHolder.ApplyCulture();
d.Invoke(s);
}, state);
}
public override SynchronizationContext CreateCopy()
{
return new CultureAwareSynchronizationContext(cultureHolder, synchronizationImplementation.CreateCopy());
}
public override string ToString()
{
return string.Format("CultureAwareSynchronizationContext: {0}", cultureHolder);
}
}
Usage:
/// code that detects Browser's culture
void Detection()
{
Thread.CurrentThread.CurrentUICulture = new CultureInfo("cs");
SynchronizationContext.SetSynchronizationContext(new CultureAwareSynchronizationContext());
}
This solution suffers from possible issues mentioned by Hans Passant.