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();
}