将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提到的可能问题 。