Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Keep CurrentCulture in async/await

I have following pseudo-code

string GetData() {   var steps = new List<Task<string>>   {     DoSomeStep(),     DoSomeStep2()   };    await Task.WhenAll(steps);    return SomeResourceManagerProxy.RetrieveValuesForLocalizedStrings( steps.Select(s => s.Result) );  } 

This method is called from WebService, where I set Thread.CurrentUICulture according to user's browser settings.

After await, the CurrentUICulture is lost (I run on different thread).

I have solved the issue with following:

    public class MyAwaiter<T> : INotifyCompletion     {         private TaskAwaiter<T> waiter;         private CultureInfo culture;          public MyAwaiter(TaskAwaiter<T> waiter)         {             this.waiter = waiter;         }          public PreserveCultureAwaiter<T> GetAwaiter() { return this; }          public bool IsCompleted { get { return waiter.IsCompleted; } }          public void OnCompleted(Action continuation)         {             culture = Thread.CurrentThread.CurrentUICulture;             waiter.OnCompleted(continuation);         }          public T GetResult()         {             Thread.CurrentThread.CurrentUICulture = culture;             return waiter.GetResult();         }     }      public static MyAwaiter<T> KeepCulture<T>(this Task<T> task)     {         return new MyAwaiter<T>(task.GetAwaiter());     }  ...      await Task.WhenAll(steps).KeepCulture(); 

This has one drawback - need for remembering to call KeepCulture() on every task that is being awaited. (I have also some extension method to keep the UI culture in task).

Is there any easier way how to preserve UI culture?

like image 991
nothrow Avatar asked Jun 05 '15 09:06

nothrow


People also ask

How to set CurrentCulture in c#?

CurrentCulture = new CultureInfo("th-TH", false); Console. WriteLine("CurrentCulture is now {0}.", CultureInfo.CurrentCulture.Name); // Display the name of the current UI culture. Console. WriteLine("CurrentUICulture is {0}.", CultureInfo.CurrentUICulture.Name); // Change the current UI culture to ja-JP.

Should you always await async methods?

If a method is declared async, make sure there is an await! If your code does not have an await in its body, the compiler will generate a warning but the state machine will be created nevertheless, adding unnecessary overhead for an operation that will actually never yield.

Is it OK to not await async?

If you forget to use await while calling an async function, the function starts executing. This means that await is not required for executing the function. The async function will return a promise, which you can use later.

Does async await improve performance?

The main benefits of asynchronous programming using async / await include the following: Increase the performance and responsiveness of your application, particularly when you have long-running operations that do not require to block the execution.


2 Answers

Culture does not flow in the .NET Framework, a very notorious problem. It is very hard to solve on Windows, culture is an unmanaged property of a thread so the CLR can't ensure it is always set correctly. That makes tinkering with the CurrentCulture on the main thread a big fat mistake. The bugs you get are very hard to diagnose. Like a SortedList you create on one thread that suddenly isn't sorted anymore on another. Yuck.

Microsoft did something about it in .NET 4.5, they added the CultureInfo.DefaultThreadCurrentCulture property. Also DefaultThreadCurrentUICulture. That still does not guarantee it will be set correctly, unmanaged code you call can change it and the CLR cannot do anything about it. In other words, a bug will be much harder to diagnose. But at least you have some idea when it might change.


UPDATE: this problem was fixed thoroughly in .NET 4.6, culture now flows from one thread to another and the CultureInfo.DefaultThreadCurrentCulture hack is not longer necessary nor useful. Documented in the MSDN article for CultureInfo.CurrentCulture. Details as written right now do not appear to be entirely correct, it always flowed when I tested it and DefaultThreadCurrentCulture appear to play no role at all anymore.

like image 176
Hans Passant Avatar answered Sep 24 '22 14:09

Hans Passant


So far I've created my own SynchronizationContext, which I've tested with both ASP.NET and console applications, and in both it keeps the culture as I want it:

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

Usage:

/// code that detects Browser's culture  void Detection() {         Thread.CurrentThread.CurrentUICulture = new CultureInfo("cs");         SynchronizationContext.SetSynchronizationContext(new CultureAwareSynchronizationContext()); } 

This solution suffers from possible issues mentioned by Hans Passant.

like image 20
nothrow Avatar answered Sep 20 '22 14:09

nothrow