Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

CurrentCulture with async/await, Custom synchronization context

I have a web application and I make use of a lot of async operations using async/await. Everything worked fine, but when I created custom tasks to run multiple things in parallel, I noticed, that within this task the current culture changes after an await. The problem seems to be, that the threadpool uses the culture of the operation system, which is different from the culture of the request and that the default synchronization does not updates the culture, even when changing the culture of the current thread within the task.

So I create a custom synchronization context:

public sealed class CulturePreservingSynchronizationContext : SynchronizationContext
{
    private CultureInfo culture;
    private CultureInfo cultureUI;

    public CulturePreservingSynchronizationContext()
    {
        GetCulture();
    }

    public void MakeCurrent()
    {
        SetCulture();

        SynchronizationContext.SetSynchronizationContext(CreateCopy());
    }

    public override SynchronizationContext CreateCopy()
    {
        CulturePreservingSynchronizationContext clonedContext = new CulturePreservingSynchronizationContext();
        clonedContext.culture = culture;
        clonedContext.cultureUI = cultureUI;

        return clonedContext;
    }

    public override void Post(SendOrPostCallback d, object state)
    {
        base.Post(s =>
        {
            SetCulture();
            d(s);
        }, state);
    }

    public override void OperationStarted()
    {
        GetCulture();

        base.OperationStarted();
    }

    private void SetCulture()
    {
        Thread.CurrentThread.CurrentCulture = culture;
        Thread.CurrentThread.CurrentUICulture = cultureUI;
    }

    private void GetCulture()
    {
        culture = CultureInfo.CurrentCulture;
        cultureUI = CultureInfo.CurrentUICulture;
    }
}

You can use it like this. In my simple example it works fine, but I have no real understanding of the very details to evaluate my approach (btw: my os-culture is de-DE). Please note that this is just an example and has nothing to do with the real code. I have just written this to demonstrate that the culture after the await is different to the culture before the await.

    Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US");

    CulturePreservingSyncContext context = new CulturePreservingSyncContext();

    Task.Run(async () =>
    {
        context.MakeCurrent();

        Console.WriteLine(CultureInfo.CurrentCulture);

        WebClient client = new WebClient();
        string s = await client.DownloadStringTaskAsync(new Uri("http://www.google.de"));

        Console.WriteLine(CultureInfo.CurrentCulture);

    }).Wait();

Any advice is really welcome to understand if the implementation of the synchronization-context is good or not and if not, if there are any better solutions. I do not want to open a discussion if async and await or tasks are good or not in my situation.

like image 510
SebastianStehle Avatar asked Mar 08 '14 18:03

SebastianStehle


1 Answers

when I created custom tasks to run multiple things in parallel

It's important to distinguish between concurrent (doing multiple things at the same time) and parallel (using multiple threads to do multiple CPU-bound operations at the same time).

I noticed, that within this task the current culture changes after an await.

If you're doing parallel code (i.e., Parallel or Task.Run), then your operations should be CPU-bound, and they shouldn't contain an await at all.

It's a good idea to avoid parallel code on the server. Task.Run on ASP.NET is almost always a mistake.

Your example code is doing asynchronous work (DownloadStringTaskAsync) in a background thread (Task.Run) and synchronously blocking on it (Wait). That doesn't make any sense.

If you have asynchronous work to do, then you can just use async-ready primitives such as Task.WhenAll. The following code will preserve culture when run on ASP.NET, and will perform three downloads simultaneously:

private async Task TestCultureAsync()
{
  Debug.WriteLine(CultureInfo.CurrentCulture);

  WebClient client = new WebClient();
  string s = await client.DownloadStringTaskAsync(new Uri("http://www.google.de"));

  Debug.WriteLine(CultureInfo.CurrentCulture);
}

var task1 = TestCultureAsync();
var task2 = TestCultureAsync();
var task3 = TestCultureAsync();
await Task.WhenAll(task1, task2, task3);
like image 170
Stephen Cleary Avatar answered Oct 24 '22 04:10

Stephen Cleary