开发者

In .Net: best way for keeping CurrentCulture on new Thread?

In a .Net 4.0 WPF project, we need to keep the same CurrentCulture on each thread then we have on the main thread.

Given, we can initialize a new thread's culture with code like the following:

  1. Keep the information in a variable (context)

    context.CurrentCulture = Thread.CurrentThre开发者_开发技巧ad.CurrentCulture;
    context.CurrentUICulture = Thread.CurrentThread.CurrentUICulture;
    
  2. On the new thread, initialize from the saved context

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

But in this age of TPL, async programming and lambda delegates, it does not feel right.

And yes, we actually can change the culture while the application is running, but that is another story.

Do you know of any setting, property or config we should initialize to keep track?


There is no good way, avoid this at all cost. The fundamental problem is that culture is not part of the Thread.ExecutionContext, it doesn't flow from one thread to another. This is a unsolvable problem, culture is a property of a native Windows thread. It will always be initialized to the system culture as selected in Control Panel's Region and Language applet.

Making temporary thread-local changes to the culture is fine, trying to switch 'the process' to another culture is a source of bugs you'll be hunting for months. The string collation order is the nastiest source of problems.


EDIT: this problem was fixed in .NET 4.5 with the CultureInfo.DefaultThreadCurrentCulture and DefaultThreadCurrentUICulture properties.


EDIT: and got the real solution in .NET 4.6, culture now flows from one thread to another. Check the MSDN article for CultureInfo.CurrentCulture for details. Beware that the description does not quite match behavior, testing required.


I don't understand why Mr. Passant warns about this and simultaneously says it's fine making "temporary" "thread-local" changes to the culture. No thread culture is truly thread-local - it's available to anything that can reference the thread via public properties, as we've seen. And if it's OK to change it for a short time, then why isn't it ok to change it for a longer time? Where do you cross the line, and why?

Nor do I really understand the OP's feeling that it "shouldn't be necessary" to write code to copy the stuff he wants to copy. You may want to put this code somewhere where you can reuse it, but apart from that I really don't see the problem with the code. In my book, it's rather more straightforward and wonderful than any lambda expression I've ever seen, and it would do the job quite nicely. Writing fancy code for the sake of it being fancy is not my style, at least.

You could do something like this:

// 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 }; }
}


By the way, it just dawns upon me that although the default culture comes from Windows' "Regional Settings", it can probably be overridden in the .NET configuration.

If this was an ASP.NET app you'd use the globalization element in web.config. I'd probably start out somewhere like here to learn about localization in WPF:

http://msdn.microsoft.com/en-us/library/ms788718.aspx#workflow_to_localize

(Is it me, or does Microsoft use the words globalization and localization, rather confusingly, interchangeably?)


When i Create Tasks with the TPL i always Pass the Culture fomr the Current UI-Thread. See sample Code below.

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

I needed to pass the uiculture to resolve the correct translation.resx for translation

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜