Как управлять NDC-подобным стеком log4net с помощью методов async/await? (стек для каждой задачи?)
В обычном / синхронном / однопоточном консольном приложении NDC.Push отлично работает для управления "текущим элементом" (возможно, на нескольких уровнях вложенности, но только на 1 уровне в этом примере).
Например:
private static ILog s_logger = LogManager.GetLogger("Program");
static void Main(string[] args)
{
BasicConfigurator.Configure();
DoSomeWork("chunk 1");
DoSomeWork("chunk 2");
DoSomeWork("chunk 3");
}
static void DoSomeWork(string chunkName)
{
using (NDC.Push(chunkName))
{
s_logger.Info("Starting to do work");
Thread.Sleep(5000);
s_logger.Info("Finishing work");
}
}
Это приведет к выводу ожидаемого журнала, показывающего запись NDC 'chunk X' справа от 'Program' (шаблон по умолчанию для базового конфигуратора)
232 [9] INFO Программный блок 1 - Начало работы
5279 [9] INFO Программный блок 1 - Отделочные работы
5279 [9] INFO Программный блок 2 - Начало работы
10292 [9] INFO Программный блок 2 - Отделочные работы
10292 [9] ИНФОРМАЦИЯ Программный блок 3 - Начало работы
15299 [9] INFO Программный блок 3 - Отделочные работы
Тем не менее, я не могу понять, как поддерживать это, используя "нормальные" асинхронные методы.
Например, пытаясь сделать это:
private static ILog s_logger = LogManager.GetLogger("Program");
static void Main(string[] args)
{
BasicConfigurator.Configure();
var task1 = DoSomeWork("chunk 1");
var task2 = DoSomeWork("chunk 2");
var task3 = DoSomeWork("chunk 3");
Task.WaitAll(task1, task2, task3);
}
static async Task DoSomeWork(string chunkName)
{
using (log4net.LogicalThreadContext.Stacks["NDC"].Push(chunkName))
//using (log4net.ThreadContext.Stacks["NDC"].Push(chunkName))
{
s_logger.Info("Starting to do work");
await Task.Delay(5000);
s_logger.Info("Finishing work");
}
}
Показывает, что все они запускаются "нормально", но когда задача завершается в другом потоке, стек теряется (я надеялся, что log4net.LogicalThreadContext будет TPL-'aware', я полагаю).
234 [10] INFO Программный блок 1 - Начало работы
265 [10] INFO Программный блок 2 - Начало работы
265 [10] ИНФОРМАЦИЯ Блок программы 3 - Начало работы
5280 [7] INFO Program (null) - Отделочные работы
5280 [12] INFO Program (null) - Отделочные работы
5280 [12] INFO Program (null) - Отделочные работы
Помимо добавления нового TaskContext (или подобного) в log4net, есть ли способ отслеживания такого рода активности?
На самом деле цель состоит в том, чтобы сделать это с помощью синтаксического сахара async / await - либо форсирование некоторого сродства потоков, либо выполнение таких вещей, как хранение параллельного словаря вокруг ключа по задаче, являются, вероятно, выполнимыми вариантами, но я стараюсь держать как можно ближе к синхронная версия кода, насколько это возможно.:)
1 ответ
Там нет хорошей истории для async
контексты логического вызова прямо сейчас.
CallContext
не может быть использовано для этого. Логичный CallContext
не понимает как async
методы возвращаются рано и возобновляются позже, поэтому он не всегда будет работать правильно для кода, который использует простой параллелизм, такой как Task.WhenAll
,
Обновить: CallContext
был обновлен в.NET 4.5 RTW для корректной работы с async
методы.
Я заглянул в log4net; LogicalThreadContext
задокументировано как использование CallContext
, но была ошибка, из-за которой он использовал нелогические контексты (исправлено в их SVN 2 февраля 2012 г.; в текущую версию 1.2.11 это исправление не включено). Даже если это исправить, он все равно не будет работать с async
(потому что логический CallContext
не работает с async
).
Когда мне нужно async
логический контекст вызова, я делаю класс, который содержит данные контекста и сохраняю все свои async
методы в функциональном стиле как члены экземпляра этого класса. Это, конечно, не идеальное решение, но это грязный взлом, который работает.
А пока, пожалуйста, поддержите предложение, чтобы Microsoft предоставила какой-то механизм для этого.
PS Параллельный словарь, основанный на Task
не будет работать, потому что async
методы не обязательно выполняют задачи (например, в вашем примере кода, на using
заявление, Task.CurrentId
было бы null
потому что нет никакой задачи, выполняемой в этот момент).
И у нити сродства тоже есть свои проблемы. В действительности вам нужен отдельный поток для каждой независимой асинхронной операции. Прощай, масштабируемость...