1
votes

I have a WPF application for .NET 4.7.2 and want to set the global culture based on command line arguments. According to many sources (e.g. here or official docs) the culture in a multithreaded application is set with CultureInfo.DefaultThreadCurrentCulture. This does work in the constructor of the App. However it does not work where I need it, namely in the Application_Startup event, where I can evaluate the command line args. Why is that and how can I fix it?

App.xaml:

<Application x:Class="WpfApp1.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:WpfApp1"
             StartupUri="MainWindow.xaml"
             Startup="Application_Startup">

    <Application.Resources>

    </Application.Resources>
</Application>

App.xaml.cs:

namespace WpfApp1
{
    public partial class App : Application
    {
        public App()
        {
            Demo("en-EN");
        }

        // Stops working if Demo is called in Application_Startup instead of constructor
        /* private void Application_Startup(object sender, StartupEventArgs e)
           {
               // TODO Take culture code from StartupEventArgs
               Demo("en-EN");
           }
        */

        private static void Demo(string cultureCode)
        {
            WriteDate();
            SetCulture(cultureCode);
            Task.Factory.StartNew(WriteDate);
        }

        private static void SetCulture(string code)
        {
            var ci = new CultureInfo(code);
            CultureInfo.DefaultThreadCurrentCulture = ci;
            CultureInfo.DefaultThreadCurrentUICulture = ci;
        }

        static void WriteDate()
        {
            var threadId = Thread.CurrentThread.ManagedThreadId;
            var threadCulture = Thread.CurrentThread.CurrentCulture;
            System.Diagnostics.Debug.WriteLine($"Thread {threadId} with culture {threadCulture} => {DateTime.Now}");
        }
    }
}

My system culture is German. Output when using constructor is as expected:

Thread 1 with culture de-DE => 09.01.2020 16:13:20
Thread 3 with culture en-EN => 1/9/2020 4:13:20 PM

Now when I move the call to Demo to the Application_Startup event the output is:

Thread 1 with culture de-DE => 09.01.2020 16:16:10
Thread 3 with culture de-DE => 09.01.2020 16:16:10

The call to CultureInfo.DefaultThreadCurrentCulture is not respected! Interestingly if I change this call to Thread.CurrentThread.CurrentCulture this fixes the problem. However I want to set the culture for all threads, not just the current one.

2
According to the link you posted, starting in .NET 4.6, culture flows between threads, so Thread.CurrentThread.CurrentCulture and Thread.CurrentThread.CurrentUICulture should change for all subsequent threads.Magnetron
Yes, you are right, that indeed explains it. However I'm still very unsure because according to the official docs DefaultThreadCurrentCulture defines the default culture and in my interpretation nothing is stated that culture flows between threads.FarwoodHill

2 Answers

1
votes

You could define a custom entry point and get the arguments from there:

public class Program
{
    static void Main(string[] args)
    {
        App app = new App(args);
        app.InitializeComponent();
        app.Run();
    }
}

public partial class App : Application
{
    public App(string[] args)
    {
        Demo("en-EN");
    }

    private static void Demo(string cultureCode)
    {
        WriteDate();
        SetCulture(cultureCode);
        for (int i = 0; i < 100; i++)
            Task.Run((Action)WriteDate);
    }

    private static void SetCulture(string code)
    {
        var ci = new CultureInfo(code);
        CultureInfo.DefaultThreadCurrentCulture = ci;
    }

    static void WriteDate()
    {
        var threadId = Thread.CurrentThread.ManagedThreadId;
        var threadCulture = Thread.CurrentThread.CurrentCulture;
        System.Diagnostics.Debug.WriteLine($"Thread {threadId} with culture {threadCulture} => {DateTime.Now}");
    }
}

Remember to change the Build Action of App.xaml to Page to prevent the compiler from generating a Main method for you.

0
votes

It is hard to answer what is the reason but looking at Threads windows I suppose thread pool handling background tasks is created before event Application_Startup is fired. You can see 1 thread in App constructor and 3 threads in startup event. Most likely 1 of those is used to handle Tasks.

I see 2 possible solutions:

1) In App constructor you can also access command line arguments using Environment so just keep logic in constructor:

Environment.GetCommandLineArgs()

2) Set it for all threads - enumerate them in Application_Startup and set culture for all using this stackoverflow answer