Асинхронный вызов службы WCF не сохраняет CurrentCulture
В соответствии с ответом в этот вопрос async/await
вызов должен сохранить CurrentCulture. В моем случае, когда я называю свои собственные методы асинхронизации, CurrentCulture сохраняется. Но если я вызываю некоторый метод службы WCF, CurrentCulture не сохраняется, он изменяется на то, что выглядит как культура потоков по умолчанию сервера.
Я посмотрел, какие управляемые потоки вызываются. Бывает так, что каждая строка кода выполнялась на одном управляемом потоке (ManagedThreadId остается тем же). И CultureInfo остается неизменным после вызова моего собственного метода асинхронизации. Но когда я вызываю метод WCF, ManagedTrheadId остается прежним, но CurrentCulture изменен.
Упрощенный код выглядит так:
private async Task Test()
{
// Befoe this code Thread.CurrentThread.CurrentCulture is "ru-RU", i.e. the default server culture
// Here we change current thread culture to some desired culture, e.g. hu-HU
Thread.CurrentThread.CurrentCulture = new CultureInfo("hu-HU");
var intres = await GetIntAsync();
// after with await Thread.CurrentThread.CurrentCulture is still "hu-HU"
var wcfres = await wcfClient.GetResultAsync();
// And here Thread.CurrentThread.CurrentCulture is "ru-RU", i.e. the default server culture
}
private async Task<int> GetIntAsync()
{
return await Task.FromResult(1);
}
wcfClient
- это экземпляр автоматически созданного клиента WCF (наследник System.ServiceModel.ClientBase
)
Все это происходит в ASP.NET MVC Web API (самообслуживании), я использую заголовок Accept-Language, чтобы установить CurrentCulture для доступа к нему позже и использовать его для возврата локализованных ресурсов. Я мог бы двигаться без CurrentCulture и просто передавать CultureInfo всем методам, но мне не нравится этот подход.
Почему CurrentThread CurrentCulture изменяется после вызова службы WCF и остается неизменным после вызова моего собственного метода асинхронизации? Может ли оно быть "исправлено"?
Ответы
Ответ 1
Сброс культуры до того, что было до await
, обычно является заданием контекста синхронизации. Но поскольку (насколько я понимаю), у вас нет контекста синхронизации, await
никак не изменит культуру потока.
Это означает, что если вы вернетесь к тому же потоку после await
, вы увидите культуру, которую вы установили. Но если вы возобновите работу в другом потоке, вы увидите культуру по умолчанию (если кто-то еще не изменил ее для этого потока тоже).
Когда вы собираетесь находиться в одном потоке после await
, когда нет контекста синхронизации? Вы гарантированно будете в том же потоке, если операция async завершится синхронно (например, ваш GetIntAsync()
), потому что в этом случае метод просто синхронно продолжается после await
. Вы также можете возобновить работу в том же потоке, если вам повезет, но вы не можете положиться на это. Это может быть недетерминированным, поэтому иногда ваш код может работать, а иногда и нет.
Что нужно делать, если вы хотите передать культуру с помощью await
, и у вас нет контекста синхронизации, который делает это для вас? В принципе, у вас есть два варианта:
- Используйте специальный awaiter (см. Stephen Toub
WithCulture()
, связанный с Noseratio). Это означает, что вам нужно добавить это ко всем своим await
, где вам нужно потопить культуру, которая может быть громоздкой.
- Используйте настраиваемый контекст синхронизации, который автоматически переместит культуру для каждого
await
. Это означает, что вы можете настроить контекст только один раз для каждой операции, и он будет работать правильно (подобно тому, как это делает контекст синхронизации ASP.NET). Это, вероятно, лучшее решение в долгосрочной перспективе.
Ответ 2
У меня нет окончательного ответа на то, почему описанное поведение может иметь место, особенно учитывая утверждение о том, что вся цепочка вызовов остается в одном потоке (ManagedThreadId
остается неизменным). Более того, мое предположение о том, что культура не течет с контекстом выполнения в AspNetSynchronizationContext
, неверно, оно действительно течет. Я взял точку из комментария @StephenCleary о попытке await Task.Delay
и проверил это со следующим небольшим исследованием:
// GET api/values/5
public async Task<string> Get(int id)
{
// my default culture is en-US
Log("Get, enter");
Thread.CurrentThread.CurrentCulture = new CultureInfo("hu-HU");
Log("Get, before Task.Delay");
await Task.Delay(200);
Thread.Sleep(200);
Log("Get, before Task.Run");
await Task.Run(() => Thread.Sleep(100));
Thread.Sleep(200);
Log("Get, before Task.Yield");
await Task.Yield();
Log("Get, before exit");
return "value";
}
static void Log(string message)
{
var ctx = SynchronizationContext.Current;
Debug.Print("{0}; thread: {1}, context: {2}, culture {3}",
message,
Thread.CurrentThread.ManagedThreadId,
ctx != null ? ctx.GetType().Name : String.Empty,
Thread.CurrentThread.CurrentCulture.Name);
}
Вывод:
Get, enter; thread: 12, context: AspNetSynchronizationContext, culture en-US
Get, before Task.Delay; thread: 12, context: AspNetSynchronizationContext, culture hu-HU
Get, before Task.Run; thread: 11, context: AspNetSynchronizationContext, culture hu-HU
Get, before Task.Yield; thread: 10, context: AspNetSynchronizationContext, culture hu-HU
Get, before exit; thread: 11, context: AspNetSynchronizationContext, culture hu-HU
Таким образом, я мог только представить, что внутри wcfClient.GetResultAsync()
что-то меняет текущую культуру потока. Обходным путем для этого может быть использование клиента awaiter, например Stephen Toub CultureAwaiter
. Однако этот симптом вызывает беспокойство. Возможно, вам нужно найти созданный код прокси-сервера WCF для "культуры" и проверить, что там происходит. Попробуйте пройти его и узнать, в какой момент Thread.CurrentThread.CurrentCulture
получает reset.