我有以下伪代码
string GetData()
{
var steps = new List<Task<string>>
{
DoSomeStep(),
DoSomeStep2()
};
await Task.WhenAll(steps);
return SomeResourceManagerProxy.RetrieveValuesForLocalizedStrings( steps.Select(s => s.Result) );
}
从WebService调用此方法,我根据用户的浏览器设置设置Thread.CurrentUICulture
。
等待之后,CurrentUICulture
丢失(我在不同的线程上运行)。
我已解决了以下问题:
public class MyAwaiter<T> : INotifyCompletion
{
private TaskAwaiter<T> waiter;
private CultureInfo culture;
public MyAwaiter(TaskAwaiter<T> waiter)
{
this.waiter = waiter;
}
public PreserveCultureAwaiter<T> 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<T> KeepCulture<T>(this Task<T> task)
{
return new MyAwaiter<T>(task.GetAwaiter());
}
...
await Task.WhenAll(steps).KeepCulture();
这有一个缺点 - 需要记住在正在等待的每个任务上调用KeepCulture()。 (我还有一些扩展方法来保持UI文化的任务)。
有没有更简单的方法来保护UI文化?
答案 0 :(得分:28)
文化不会流入.NET Framework,这是一个非常臭名昭着的问题。在Windows上很难解决,culture是线程的unmanaged property,因此CLR无法确保它始终设置正确。这使得在主线程上修补CurrentCulture是一个很大的错误。你得到的错误很难诊断。就像您在一个线程上创建的SortedList一样,突然不再对另一个线程进行排序。呸。
微软在.NET 4.5中做了一些事情,他们添加了CultureInfo.DefaultThreadCurrentCulture属性。还有DefaultThreadCurrentUICulture。这仍然无法保证它将被正确设置,您调用的非托管代码可以更改它,CLR无法对其进行任何操作。换句话说,一个bug将难以诊断。但至少你有一些想法可能会改变。
更新:在.NET 4.6中彻底解决了这个问题,文化现在从一个线程流向另一个线程,而CultureInfo.DefaultThreadCurrentCulture hack不再是必需的,也不是有用的。在CultureInfo.CurrentCulture的MSDN文章中记录。现在写的详细信息似乎并不完全正确,当我测试它时它总是流动,而DefaultThreadCurrentCulture似乎根本不再起作用。
答案 1 :(得分:3)
到目前为止,我已经创建了自己的SynchronizationContext
,我已经使用ASP.NET和控制台应用程序进行了测试,并且它保留了我想要的文化:
/// <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);
}
}
用法:
/// code that detects Browser's culture
void Detection()
{
Thread.CurrentThread.CurrentUICulture = new CultureInfo("cs");
SynchronizationContext.SetSynchronizationContext(new CultureAwareSynchronizationContext());
}
此解决方案可能会遇到issues mentioned by Hans Passant。
答案 2 :(得分:1)
official doc 中有很好的解释。
不久,对于 .Net 4.6+,您需要在 calling 线程中,在 await
之前设置文化。
因此,文化将从当前线程传递到所有下一个 async/await
(读作后续线程)。
public async Task SomeFunction(){
Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo(locale);
Thread.CurrentThread.CurrentCulture = Thread.CurrentThread.CurrentUICulture;
await OtherFunction();
}