Подтвердить что ты не робот

Сохранить CurrentCulture в async/wait

У меня есть следующий псевдокод

string GetData()
{
  var steps = new List<Task<string>>
  {
    DoSomeStep(),
    DoSomeStep2()
  };

  await Task.WhenAll(steps);

  return SomeResourceManagerProxy.RetrieveValuesForLocalizedStrings( steps.Select(s => s.Result) );

}

Этот метод вызывается из WebService, где я устанавливаю Thread.CurrentUICulture в соответствии с настройками браузера пользователя.

После ожидания CurrentUICulture теряется (я запускаю в другом потоке).

Я решил проблему следующим образом:

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

У этого есть один недостаток - необходимость запоминать вызов KeepCulture() для каждой ожидаемой задачи. (У меня также есть метод расширения, чтобы сохранить культуру пользовательского интерфейса в задаче).

Есть ли более простой способ сохранения культуры пользовательского интерфейса?

4b9b3361

Ответ 1

Культура не протекает в .NET Framework, очень печально известной проблеме. Культура - это неуправляемое свойство в потоке, поэтому CLR не может гарантировать, что она всегда установлена ​​правильно. Это делает переделку с CurrentCulture на основной нити большой толстой ошибкой. Ошибки, которые вы получаете, очень трудно диагностировать. Как SortedList, вы создаете на одном потоке, который внезапно не сортируется больше на другом. Тьфу.

Microsoft сделала кое-что об этом в .NET 4.5, они добавили свойство CultureInfo.DefaultThreadCurrentCulture. Также DefaultThreadCurrentUICulture. Это все еще не гарантирует, что он будет установлен правильно, неуправляемый код, который вы вызываете, может изменить его, и CLR ничего не может с этим поделать. Другими словами, ошибка будет намного сложнее диагностировать. Но по крайней мере у вас есть идея, когда это может измениться.


UPDATE: эта проблема была тщательно исправлена ​​в .NET 4.6, теперь культура перетекает из одного потока в другой, а хакер CultureInfo.DefaultThreadCurrentCulture больше не нужен и не нужен. Документировано в статье MSDN для CultureInfo.CurrentCulture. Детали, как написано прямо сейчас, не выглядят абсолютно корректными, они всегда текли, когда я тестировал его, и DefaultThreadCurrentCulture больше не играет никакой роли.

Ответ 2

Пока я создал свой собственный SynchronizationContext, который я тестировал как с ASP.NET, так и с консольными приложениями, и в обоих он сохраняет культуру, как я ее хочу:

/// <summary>
/// Class that captures current thread 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 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);
    }
}

Использование:

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

Это решение страдает от возможных проблем, упомянутых Hans Passant.