Как управлять NDC-подобным стеком log4net с помощью методов async / await? (стек для каждой задачи?) - PullRequest
13 голосов
/ 20 марта 2012

В обычном / синхронном / однопоточном консольном приложении NDC.Push отлично работает для управления «текущим элементом» (возможно, на нескольких уровнях вложенности, но только для одного уровня в этом примере).

Например:

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] ИНФОРМАЦИЯ Программный блок 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-'ware ', я думаю).

234 [10] INFO Программный блок 1 - начало работы

265 [10] INFO Программный блок 2 - начало работы

265 [10] INFO Программный блок 3 - начало работы

5280 [7] Программа INFO (ноль) - Отделочные работы

5280 [12] Программа INFO (ноль) - Отделочные работы

5280 [12] Программа INFO (ноль) - Отделочные работы

Помимо добавления нового TaskContext (или подобного) в log4net, есть ли способ отслеживания такого рода активности?

На самом деле цель состоит в том, чтобы сделать это с помощью синтаксического сахара async / await - либо форсирование некоторого сродства потоков, либо выполнение таких вещей, как хранение параллельного словаря вокруг ключа с помощью задачи, вероятно, выполнимые варианты, но я пытаюсь сохранить как близко к синхронной версии кода, насколько это возможно. :)

1 Ответ

16 голосов
/ 20 марта 2012

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

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

...