将CurrentCulture保持在异步/等待状态

我有以下伪代码

string GetData() { var steps = new List<Task> { DoSomeStep(), DoSomeStep2() }; await Task.WhenAll(steps); return SomeResourceManagerProxy.RetrieveValuesForLocalizedStrings( steps.Select(s => s.Result) ); } 

从WebService调用此方法,我根据用户的浏览器设置设置Thread.CurrentUICulture

等待之后, CurrentUICulture丢失了(我在不同的线程上运行)。

我用以下方法解决了这个问题:

  public class MyAwaiter : INotifyCompletion { private TaskAwaiter waiter; private CultureInfo culture; public MyAwaiter(TaskAwaiter waiter) { this.waiter = waiter; } public PreserveCultureAwaiter GetAwaiter() { return this; } public bool IsCompleted { get { return waiter.IsCompleted; } } public void OnCompleted(Action continuation) { culture = Thread.CurrentThread.CurrentUICulture; waiter.OnCompleted(continuation); } public T GetResult() { Thread.CurrentThread.CurrentUICulture = culture; return waiter.GetResult(); } } public static MyAwaiter KeepCulture(this Task task) { return new MyAwaiter(task.GetAwaiter()); } ... await Task.WhenAll(steps).KeepCulture(); 

这有一个缺点 – 需要记住在正在等待的每个任务上调用KeepCulture()。 (我还有一些扩展方法来保持UI文化的任务)。

有没有更简单的方法来保护UI文化?

文化不会流入.NET Framework,这是一个非常臭名昭着的问题。 在Windows上很难解决,culture是线程的非托管属性 ,因此CLR无法确保它始终正确设置。 这使得在主线程上修补CurrentCulture是一个很大的错误。 你得到的错误很难诊断。 就像在一个线程上创建的SortedList一样,突然不再在另一个线程上进行排序。 呸。

微软在.NET 4.5中做了一些事情,他们添加了CultureInfo.DefaultThreadCurrentCulture属性。 还有DefaultThreadCurrentUICulture。 这仍然无法保证它将被正确设置,您调用的非托管代码可以更改它,CLR无法对其进行任何操作。 换句话说,一个bug将难以诊断。 但至少你有一些想法,它可能会改变。


更新:在.NET 4.6中彻底修复了这个问题,文化现在从一个线程流向另一个线程,而CultureInfo.DefaultThreadCurrentCulture hack不再是必需的,也不是有用的。 在CultureInfo.CurrentCulture的MSDN文章中记录。 现在写的详细信息似乎并不完全正确,当我测试它时它总是流动,而DefaultThreadCurrentCulture似乎根本不再起作用。

到目前为止,我已经创建了自己的SynchronizationContext ,我已经使用ASP.NET和控制台应用程序进行了测试,并且它保留了我想要的文化:

 ///  /// Class that captures current thread's culture, and is able to reapply it to different one ///  internal sealed class ThreadCultureHolder { private readonly CultureInfo threadCulture; private readonly CultureInfo threadUiCulture; ///  /// Captures culture from currently running thread ///  public ThreadCultureHolder() { threadCulture = Thread.CurrentThread.CurrentCulture; threadUiCulture = Thread.CurrentThread.CurrentUICulture; } ///  /// Applies stored thread culture to current thread ///  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); } } ///  /// SynchronizationContext that passes around current thread's culture ///  internal class CultureAwareSynchronizationContext : SynchronizationContext { private readonly ThreadCultureHolder cultureHolder; private readonly SynchronizationContext synchronizationImplementation; ///  /// Creates default SynchronizationContext, using current(previous) SynchronizationContext /// and captures culture information from currently running thread ///  public CultureAwareSynchronizationContext() : this(Current) {} ///  /// Uses passed SynchronizationContext (or null, in that case creates new empty SynchronizationContext) /// and captures culture information from currently running thread ///  ///  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); } } 

用法:

 /// code that detects Browser's culture void Detection() { Thread.CurrentThread.CurrentUICulture = new CultureInfo("cs"); SynchronizationContext.SetSynchronizationContext(new CultureAwareSynchronizationContext()); } 

该解决方案遇到Hans Passant提到的可能问题 。