在 .Net 中:保持 CurrentCulture 在新线程中的最佳方法是什么?

24
在一个 .Net 4.0 的 WPF 项目中,我们需要在其他线程上与主线程上保持相同的当前区域性(CurrentCulture)。
我们可以使用以下代码初始化新线程的区域性:
  1. 将信息保存在一个变量中(context)
context.CurrentCulture = Thread.CurrentThread.CurrentCulture;
context.CurrentUICulture = Thread.CurrentThread.CurrentUICulture;
  • 在新线程中,从保存的上下文中进行初始化

  • Thread.CurrentThread.CurrentCulture = context.CurrentCulture;
    Thread.CurrentThread.CurrentUICulture = context.CurrentUICulture;
    

    但在这个TPL、异步编程和lambda委托的时代,它感觉不太对。

    当然,我们实际上可以在应用程序运行时更改文化,但那是另一回事。

    你知道我们应该初始化哪些设置、属性或配置来进行跟踪吗?

    4个回答

    25

    避免这种情况是没有好方法的。基本问题在于文化不是Thread.ExecutionContext的一部分,它不能从一个线程流动到另一个线程。这是一个无法解决的问题,文化是本地Windows线程的属性。它始终会初始化为在控制面板的区域和语言应用程序中选择的系统文化。

    对文化进行临时的线程本地更改是可以的,但试图将“进程”切换到另一个文化是一个你将要寻找几个月的错误源头。字符串排序顺序是最难处理的问题之一。


    编辑:在.NET 4.5中通过CultureInfo.DefaultThreadCurrentCulture和DefaultThreadCurrentUICulture属性解决了此问题。


    编辑:在.NET 4.6中得到了真正的解决方案,现在文化从一个线程流动到另一个线程。请查看MSDN文章CultureInfo.CurrentCulture以获取详细信息。请注意,描述与行为并不完全匹配,需要进行测试。


    1
    并在 .NET 4.6 中进一步修复,成为 ExecutionContext 的一部分。 - canton7

    5
    我不明白Passant先生为什么会警告这个问题,同时又说对于文化的“临时”、“线程本地”更改是可以的。没有线程文化是真正的线程本地 - 它对任何可以通过公共属性引用线程的东西都是可用的,正如我们所看到的那样。如果可以短时间更改它,那么为什么不能长时间更改它呢?你在哪里划线,为什么?
    我也不太理解OP认为“不应该必须”编写代码来复制他想要复制的内容的感觉。你可能希望将此代码放在某个地方以便重用,但除此之外,我真的看不出代码的问题。在我看来,这比我见过的任何lambda表达式都更加简单明了,而且它可以很好地完成工作。为了追求花哨的代码而写花哨的代码不是我的风格,至少不是现在。
    你可以像这样做:
    // Program.cs
    static CultureInfo culture, uiCulture;
    
    [STAThread]
    static public void Main()
    {
       var t = Thread.CurrentThread;
       culture = t.CurrentCulture;
       uiCulture = t.CurrentUICulture;
    }
    
    static public Thread CreateThread() 
    {
        return new Thread() { CurrentCulture = culture, CurrentUICulture = uiCulture }; }
    }
    

    而且,对于在你的控制之外创建的线程(例如线程池线程),CurrentCulture 变得更加难以理解。 - CodesInChaos
    1
    ThreadPool.QueueUserWorkItem(...)的委托中,您可以使用静态的Thread.CurrentThread.CurrentCulture = myCulture。您还可以将所有内容封装在一个帮助方法中。 - herzmeister

    3
    顺带一提,我突然想到虽然默认文化设置来源于Windows的“区域设置”,但它可能会被.NET配置覆盖。
    如果这是一个ASP.NET应用程序,则可以在web.config中使用globalization元素。我可能会从这里开始学习WPF本地化:http://msdn.microsoft.com/en-us/library/ms788718.aspx#workflow_to_localize (是我自己的问题,还是Microsoft混淆了全球化和本地化这两个词汇?)

    3
    实际上,你上一个问题的答案是否定的,“全球化”是为了后续“本地化”而进行的准备过程。也就是说,一个应用程序可能完全进行了全球化,除了默认语言版本外,没有任何其他可用的本地化版本。 - Anton Tykhyy

    2

    当我使用TPL创建任务时,我总是从当前的UI线程传递Culture。请见下面的示例代码。

    private void WorkProcessingAsync(IWorkItem workItem)
            {
                IsBusy = true;
                /* =============================
                *  Create a TPL Task and pass the current UiCulture in an state Object to resolve the correct .resx file for translation / globalisation / Multilanguate features in Background Thread
                * ==============================*/
                Task<IWorkItem> task = Task.Factory.StartNew((stateObj) =>
                {
                    // here we are already in the task background thread
                    // save cast the given stateObj
                    var tuple = stateObj as Tuple<IWorkItem, CultureInfo>;
    
                    Debug.Assert(tuple != null, "tuple != null");
    
                    Thread.CurrentThread.CurrentUICulture = tuple.Item2;   // Here we set the UI-Thread Culture to the Background Thread
    
                    var longRunningOperationAnswer = LongRunningOperation.DoLongWork(tuple.Item1);
                    return longRunningOperationAnswer;
    
                }, new Tuple<IWorkItem, CultureInfo>(workItem, Thread.CurrentThread.CurrentUICulture));  // here we pass the UI-Thread Culture to the State Object
    
    
    
                /* =======================================================================
                *   Handle OnlyOnRanToCompletion Task and process longRunningOperationAnswer back in UiThread
                * =======================================================================*/
                task.ContinueWith((t) =>
                {
                    IsBusy = false;
                    // handle longRunningOperationAnswer here in t.Result
                    Log.Debug("Operation completet with {0}", t.Result);
    
                }, CancellationToken.None
                , TaskContinuationOptions.OnlyOnRanToCompletion
                , TaskScheduler.FromCurrentSynchronizationContext());
    
                /* =======================================================================
             *   Handle OnlyOnFaulted Task back in UiThread
             * =======================================================================*/
                task.ContinueWith((t) =>
                {
                    IsBusy = false;
                    AggregateException aggEx = t.Exception;
    
                    if (aggEx != null)
                    {
                        aggEx.Flatten();
                        Log.ErrorFormat("The Task exited with Exception(s) \n{0}", aggEx);
                        foreach (Exception ex in aggEx.InnerExceptions)
                        {
                            if (ex is SpecialExaption)
                            {
                                //Handle Ex here
                                return;
                            }
                            if (ex is CustomExeption)
                            {
                                //Handle Ex here
                                return;
                            }
                        }
                    }
                }, CancellationToken.None
                , TaskContinuationOptions.OnlyOnFaulted
                , TaskScheduler.FromCurrentSynchronizationContext());
            }
    

    我需要传递uiculture以解决正确的translation.resx翻译


    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接