0

Background:

I have an async await function that gets a language and locale parameters from an external source and this will be used to set to the CurrentUICulture.

My web application is built in .NET Core 3.1 using C#

Problem:

The CurrentUICulture is set only in the context of Child method and once it goes out of this scope, CurrentUICulture doesn't reflect the changed value.

My Predicament:

I know it is always recommended to set the value of CurrentUICulture from main thread (and not from a child thread), but I am working on an existing code where there is too much of dependency on Thread.CurrentThread.CurrentUICulture and it is practically difficult for me to change at various places.

Snippet:

public class Example
{
   public static async Task Main()
   {
      Console.WriteLine("Main Thread: Current UI culture is {0}",
                        Thread.CurrentThread.CurrentUICulture.Name);
     
      await Child();      

      Console.WriteLine("Main Thread: UI culture after change is {0}",
                        Thread.CurrentThread.CurrentUICulture.Name);
   }
   public static async Task Child()
   {
      //get the culture text from an external service (assume this will return "pt-BR")
      var cultureText = await externalService.GetCultureAsync();

      //set the culture
      Thread.CurrentThread.CurrentUICulture = new CultureInfo(cultureText);
      Console.WriteLine("Child Thread: UI culture changed to {0}",
                        Thread.CurrentThread.CurrentUICulture.Name);
   }
}

Actual Output:

(Assume the value returned by external service is "pt-BR" [Portugese Brazil])

Main Thread: Current UI culture is en-US
Child Thread: UI culture changed to pt-BR
Main Thread: UI culture after change is en-US

Desired Output:

Main Thread: Current UI culture is en-US
Child Thread: UI culture changed to pt-BR
Main Thread: UI culture after change is pt-BR

Any leads to solve the issue would be highly appreciable.

Heretic Monkey
  • 11,687
  • 7
  • 53
  • 122
  • *Don't* set the thread Culture. Pass it explicitly to any formatting or parsing method that acceps an `IFormatProvider`. There's no reason to set either the parent or the child thread culture itself – Panagiotis Kanavos Apr 08 '21 at 15:22
  • 3
    Manipulating `Thread.CurrentThread` from a `Task` is a bad idea to begin with, since you have no guarantees about what thread a `Task` ends up on. This is a global solution to a local problem. If you must pin code to a thread because it manipulates the culture, then do so, with explicit creation and managing of `Thread`s. This is awkward of course, but that's what you get with thread affinity. – Jeroen Mostert Apr 08 '21 at 15:22
  • Why wouldn't you just have the child task return the culture string to the main thread and have the main thread set the culture? – Heretic Monkey Apr 08 '21 at 15:26
  • @HereticMonkey - It's not so straight forward as you see in my snippet. The actual code is way too complex. – Ramalingam S Apr 08 '21 at 15:32
  • @JeroenMostert - I know the idea is bad, but I am helpless. There are so many places in my code such manipulations happen and I hardly have any control over it. – Ramalingam S Apr 08 '21 at 15:37
  • @PanagiotisKanavos - Yes, not using the thread is the last resort. – Ramalingam S Apr 08 '21 at 15:39
  • 1
    The idea isn't just *bad*, it's "not doable as given". Either do not use `Task`s or do not use `Thread.CurrentCulture`, that's the basic gist. It is theoretically possible to do things with custom schedulers and custom contexts passed along with tasks to ensure the culture is captured, but these solutions are even more work than fixing the code, and certainly less maintainable ultimately. Note that there is no notion of "parent" or "child" between threads; only tasks have something like that (and even there it's seldom used). – Jeroen Mostert Apr 08 '21 at 15:40
  • @JeroenMostert - Agree. Perhaps, it is high time for me to achieve this in a better way. I don't mind doing some refactoring to make it logical and workable. – Ramalingam S Apr 08 '21 at 15:44
  • @RamalingamS on the contrary, this is the first and best option. It's the way .NET works. In fact you get analyzer warnings when you use eg `Parse` or `ToString` without passing a `CultureInfo` explicitly. *Why* are you trying to set the CultureInfo on the thread instead of passing it around? It would only make sense to do so if you want to change the language the UI is displayed in – Panagiotis Kanavos Apr 08 '21 at 16:18
  • @RamalingamS the culture in this case is just data, *NOT* the culture of the child thread. This means you can return it as data too, and use it wherever you want. You can set it as the main thread's UI culture to use localized resources for rendering – Panagiotis Kanavos Apr 08 '21 at 16:21

1 Answers1

1

I assume you don't want your main thread to do anything else until it has its culture set. That seems like a reasonable assumption to me.

In that case, you can just block the main thread:

public static async Task Main()
{
  // Nothing should use await before this line.
  var cultureText = externalService.GetCultureAsync().GetAwaiter().GetResult();
  Thread.CurrentThread.CurrentUICulture = new CultureInfo(cultureText);

  // Other code that can use await.
}

If this is a UI app, then you can run the asynchronous code on a background thread:

public static async Task Main()
{
  // Nothing should use await before this line.
  var cultureText = Task.Run(() => externalService.GetCultureAsync()).GetAwaiter().GetResult();
  Thread.CurrentThread.CurrentUICulture = new CultureInfo(cultureText);

  // Other code that can use await.
}
Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810