Как управлять 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 потому что нет никакой задачи, выполняемой в этот момент).

И у нити сродства тоже есть свои проблемы. В действительности вам нужен отдельный поток для каждой независимой асинхронной операции. Прощай, масштабируемость...

Другие вопросы по тегам