В этом question принятый ответ Стивена Клири говорит, что LogicalCallContext не может корректно работать с async. Он также опубликовал об этом в этом потоке MSDN.
LogicalCallContext сохраняет Hashtable, сохраняя данные, отправленные в CallContext.LogicalGet/SetData. И это только мелкая копия этого Hashtable. Поэтому, если вы храните в нем изменяемый объект, разные задачи/потоки будут видеть друг друга. Вот почему пример программы Стивена Клири (NDC), опубликованный в этом потоке MSDN, работает некорректно.
Но AFAICS, если вы сохраняете неизменяемые данные в Hashtable (возможно, используя неизменяемые коллекции), это должно работать, и давайте внедрить NDC.
Однако, Стивен Клири также сказал в этом принятом ответе:
CallContext не может быть использован для этого. Microsoft специально рекомендуется против использования CallContext для чего угодно, кроме удаленных. Более того, логический CallContext не понимает, как методы async возвращаются раньше и возобновляются позже.
К сожалению, эта ссылка на рекомендацию Microsoft недоступна (страница не найдена). Поэтому мой вопрос: почему это не рекомендуется? Почему я не могу использовать LogicalCallContext таким образом? Что значит сказать, что он не понимает асинхронные методы? Из вызывающего POV они просто методы, возвращающие задачи, нет?
ETA: см. также этот другой вопрос. Там ответ Стивена Клири говорит:
вы можете использовать CallContext.LogicalSetData и CallContext.LogicalGetData, но я рекомендую вам не потому, что они не поддерживают какой-либо "клонирование" при использовании простого parallelism
Это похоже на мое дело. Поэтому я должен был бы создать NDC, который на самом деле нужен мне, а не для log4net.
Я написал несколько примеров кода и, похоже, работает, но простое тестирование не всегда ловит concurrency ошибки. Итак, поскольку в этих других сообщениях есть намеки, что это может не сработать, я все еще спрашиваю: действительно ли этот подход?
ETA: Когда я запускаю Стивена, предлагаю повторить из приведенного ниже ответа), я не получаю неправильных ответов, которые он говорит, что я хочу, я получаю правильные ответы. Даже там, где он сказал: "Значение LogicalCallContext здесь всегда" 1 ", я всегда получаю правильное значение 0. Возможно, это связано с состоянием гонки? Во всяком случае, я все еще не воспроизвел какие-либо проблемы на своем компьютере. Вот точный код, который я запускаю; он печатает только" истину "здесь, где Стивен говорит, что должен печатать" ложь" хотя бы некоторое время.
private static string key2 = "key2";
private static int Storage2 {
get { return (int) CallContext.LogicalGetData(key2); }
set { CallContext.LogicalSetData(key2, value);}
}
private static async Task ParentAsync() {
//Storage = new Stored(0); // Set LogicalCallContext value to "0".
Storage2 = 0;
Task childTaskA = ChildAAsync();
// LogicalCallContext value here is always "1".
// -- No, I get 0
Console.WriteLine(Storage2 == 0);
Task childTaskB = ChildBAsync();
// LogicalCallContext value here is always "2".
// -- No, I get 0
Console.WriteLine(Storage2 == 0);
await Task.WhenAll(childTaskA, childTaskB);
// LogicalCallContext value here may be "0" or "1".
// -- I always get 0
Console.WriteLine(Storage2 == 0);
}
private static async Task ChildAAsync() {
var value = Storage2; // Save LogicalCallContext value (always "0").
Storage2 = 1; // Set LogicalCallContext value to "1".
await Task.Delay(1000);
// LogicalCallContext value here may be "1" or "2".
Console.WriteLine(Storage2 == 1);
Storage2 = value; // Restore original LogicalCallContext value (always "0").
}
private static async Task ChildBAsync() {
var value = Storage2; // Save LogicalCallContext value (always "1").
Storage2 = 2; // Set LogicalCallContext value to "2".
await Task.Delay(1000);
// LogicalCallContext value here may be "0" or "2".
Console.WriteLine(Storage2 == 2);
Storage2 = value; // Restore original LogicalCallContext value (always "1").
}
public static void Main(string[] args) {
try {
ParentAsync().Wait();
}
catch (Exception e) {
Console.WriteLine(e);
}
Итак, мой повторный вопрос: что (если что-либо) неправильно с приведенным выше кодом?
Кроме того, когда я смотрю код для CallContext.LogicalSetData, он вызывает Thread.CurrentThread.GetMutableExecutionContext() и модифицирует это. И GetMutableExecutionContext говорит:
if (!this.ExecutionContextBelongsToCurrentScope)
this.m_ExecutionContext = this.m_ExecutionContext.CreateMutableCopy();
this.ExecutionContextBelongsToCurrentScope = true;
И CreateMutableCopy в конце концов делает мелкую копию LogicalCallContext Hashtable, которая содержит данные, предоставленные пользователем.
Итак, пытаясь понять, почему этот код не работает для Stephen, это потому, что иногда ExecutionContextBelongsToCurrentScope имеет неправильное значение? Если это так, возможно, мы заметим, когда это произойдет - увидев, что изменился либо текущий идентификатор задачи, либо текущий идентификатор потока, и вручную сохраните отдельные значения в нашей неизменяемой структуре, с ключом с помощью идентификатора thread + task. (С этим подходом возникают проблемы с производительностью, например, сохранение данных для мертвых задач, но помимо этого они будут работать?)