C# 将CurrentCulture保持为异步/等待
我有以下伪代码C# 将CurrentCulture保持为异步/等待,c#,asynchronous,C#,Asynchronous,我有以下伪代码 string GetData() { var steps = new List<Task<string>> { DoSomeStep(), DoSomeStep2() }; await Task.WhenAll(steps); return SomeResourceManagerProxy.RetrieveValuesForLocalizedStrings( steps.Select(s => s.Result
string GetData()
{
var steps = new List<Task<string>>
{
DoSomeStep(),
DoSomeStep2()
};
await Task.WhenAll(steps);
return SomeResourceManagerProxy.RetrieveValuesForLocalizedStrings( steps.Select(s => s.Result) );
}
这有一个缺点——需要记住在等待的每个任务上调用KeepCulture()。(我还有一些扩展方法来保持任务中的UI区域性)
有没有更简单的方法来保存UI区域性?区域性不在.NET Framework中流动,这是一个非常臭名昭著的问题。在Windows上很难解决这个问题,区域性是线程的一部分,因此CLR无法确保它总是正确设置。这使得在主线上修补当前文化成为一个巨大的错误。你得到的bug很难诊断。就像在一个线程上创建的排序列表,突然在另一个线程上不再排序。恶心 微软在.NET4.5中做了一些改变,他们添加了这个属性。也可以使用默认的threadcurrentui文化。这仍然不能保证它将被正确设置,您调用的非托管代码可以更改它,而CLR对此无能为力。换句话说,一个bug将更难诊断。但至少你知道什么时候会改变
更新:这个问题在.NET 4.6中被彻底修复,区域性现在从一个线程流向另一个线程,CultureInfo.DefaultThreadCurrentCulture黑客不再是必要的,也不再有用。记录在MSDN文章中。目前编写的详细信息似乎并不完全正确,在我测试它时,它始终是流动的,DefaultThreadCurrentCulture似乎不再扮演任何角色。到目前为止,我已经创建了自己的
同步上下文
,我已经用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);
}
}
这个解决方案可能会遇到问题。这是一个很好的解释
对于.Net 4.6+,您需要在等待
之前,在调用线程中设置区域性。
因此,区域性将从当前线程传递到所有下一个async/await
(读作后续线程)
注意:我正在为已经用async/await编写的东西编写本地化框架。我希望一切对其他开发人员尽可能透明。我刚才在一个.NET存储库中看到过类似的解决方案。可能是EF。我想他们删除它是出于某种原因,所以你需要扫描日志。也许是时候结束这个Q+A了,问题已经解决了。这不起作用
OnCompleted
从未被调用。我理解您的建议,但不幸的是,不这样做是我目前无法做到的事情。我认为这应该被标记为答案。在我看来,这似乎是框架4.5中一个合适的解决方案。非托管调用可以更改区域性的论点不相关。默认值也可以在托管调用中重写。或者我不太理解。(?)问题可能是为什么任何非托管代码都应该改变文化。我认为通常情况下,您可以为流程中的所有线程定义区域性。这是目前最好的解决方案。请注意,有几个ASP.NET帮助程序方法假定当前上下文为AspNetSynchronizationContext
。因此,必须小心使用此解决方案。替换内置的ASP.NET同步上下文似乎非常具有侵入性。此外,这可能会打破框架和库对同步上下文做什么或不做什么的假设。同步上下文是全局状态-请小心。@StephenCleary,你能更具体一点吗?我发现,Page.cs中只直接引用了AspNetSynchronizatonContext
,例如,这些引用用于异步加载ASP.NET控件。我的SynchronizationContext
应该具有与以前使用的SynchronizationContext
相同的行为(除了当前文化)-它包装并调用上一个。@Yossarian:我在ASP.NET MVC子操作中观察到了这种行为。如果有人好奇,ASP.NET Core不会使用同步上下文,所以这不应该是个问题。
/// <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());
}
public async Task SomeFunction(){
Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo(locale);
Thread.CurrentThread.CurrentCulture = Thread.CurrentThread.CurrentUICulture;
await OtherFunction();
}