Отслеживание вложенных многопоточных операций - PullRequest
4 голосов
/ 16 апреля 2010

У меня есть похожий код

void ExecuteTraced(Action a, string message)
{
    TraceOpStart(message);
    a();
    TraceOpEnd(message);
}

Функция обратного вызова (a) может снова вызвать ExecuteTraced, а в некоторых случаях асинхронно (через ThreadPool, BeginInvoke, PLINQ и т. Д., Поэтому у меня нет возможности явно пометить область действия операции). Я хочу отследить все вложенные операции (даже если они выполняются асинхронно). Итак, мне нужна возможность получить последнюю отслеженную операцию в контексте логического вызова (может быть много параллельных потоков, поэтому невозможно использовать статическое поле lastTraced).

Существуют CallContext.LogicalGetData и CallContext.LogicalSetData, но, к сожалению, LogicalCallContext передает изменения обратно в родительский контекст при вызове EndInvoke (). Еще хуже, это может произойти в любой момент, если EndInvoke () был вызван как асинхронный. EndInvoke меняет текущий CallContext - почему?

Также существует Trace.CorrelationManager, но он основан на CallContext и имеет все те же проблемы.

Существует обходной путь: используйте свойство CallContext.HostContext, которое не распространяется обратно после завершения асинхронной операции. Кроме того, он не клонируется, поэтому значение должно быть неизменным - не проблема. Тем не менее, он используется HttpContext, поэтому обходной путь не применим в приложениях Asp.Net.

Единственный способ, которым я вижу, - это обернуть HostContext (если не мой) или весь LogicalCallContext в динамический и отправить все вызовы, кроме последней отслеживаемой операции.

1 Ответ

6 голосов
/ 21 апреля 2010

Хорошо, я отвечаю сам.

Короткий: решения нет.

Немного подробно:

Проблема в том, что мне нужен способ сохранения последней активной операции для каждого логического контекста. Код трассировки не будет контролировать поток выполнения, поэтому невозможно передать lastStartedOperation в качестве параметра. Контекст вызова может клонироваться (например, если запущен другой поток), поэтому мне нужно клонировать значение как клоны контекста.

CallContext.LogicalSetData () хорошо подходит, но он объединяет значения в исходный контекст по завершении асинхронной операции (фактически заменяя все изменения, сделанные до вызова EndInvoke). Теоретически, это может происходить даже асинхронно, давая непредсказуемый результат CallContext.LogicalGetData ().

Я говорю теоретически, потому что простой вызов a.EndInvoke () внутри asyncCallback не заменяет значения в исходном контексте. Хотя я не проверял поведение удаленных вызовов (и, похоже, WCF вообще не поддерживает CallContext). Кроме того, документация (старая) гласит:

Метод BeginInvoke передает CallContext на сервер. когда Вызывается метод EndInvoke, CallContext сливается обратно на нить. Это включает случаи, когда BeginInvoke и EndInvoke называются последовательно и где BeginInvoke вызывается в одном потоке и EndInvoke вызывается функцией обратного вызова.

Последняя версия не так определена:

Метод BeginInvoke передает CallContext на сервер. Когда Вызывается метод EndInvoke, данные содержится в CallContext копируется вернуться к теме, которая называется BeginInvoke.

Если вы покопаетесь в источнике фреймворка, вы обнаружите, что значения фактически хранятся внутри хеш-таблицы внутри LogicalCallContext внутри текущего ExecutionContext текущего потока.

Когда вызывается контекстный клон (например, при BeginInvoke) вызывается LogicalCallContext.Clone. И EndInvoke (по крайней мере, когда вызывается внутри исходного CallContext) вызывает LogicalCallContext.Merge (), заменяя старые значения внутри m_Datastore новыми.

Итак, нам нужно как-то указать значение, которое будет клонировано, но не объединено обратно.

LogicalCallContext.Clone () также клонирует (без слияния) содержимое двух приватных полей, m_RemotingData и m_SecurityData. Поскольку типы полей определены как внутренние, вы не можете получить их (даже с помощью emit), добавить свойство MyNoFlowbackValue и заменить значение поля m_RemotingData (или другого) на экземпляр производного класса.

Кроме того, типы полей не являются производными от MBR, поэтому их нельзя обернуть, используя прозрачный прокси.

Вы не можете наследовать от LogicalCallContext - он запечатан. (На самом деле, вы могли бы - если бы использовали API профилирования CLR для замены IL, как это делают фиктивные фреймворки. Не желаемое решение.)

Вы не можете заменить значение m_Datastore, поскольку LogicalCallContext сериализует только содержимое хеш-таблицы, а не саму хеш-таблицу.

Последнее решение - использовать CallContext.HostContext. Это эффективно сохраняет данные в поле m_hostContext LogicalCallContext. LogicalCallContext.Clone () разделяет (не клонирует) значение m_hostContext, поэтому значение должно быть неизменным. Хотя это не проблема.

И даже это не работает, если используется HttpContext, поскольку он устанавливает свойство CallContext.HostContext, заменяющее старое значение. По иронии судьбы, HttpContext не реализует ILogicalThreadAffinative и, следовательно, не сохраняется как значение поля m_hostContext. Он просто заменяет старое значение на ноль.

Итак, решения нет и не будет, так как CallContext является частью удаленного взаимодействия, а удаленное взаимодействие устарело.

PS Thace.CorrelationManager использует CallContext внутри и, следовательно, не работает должным образом.Кстати, LogicalCallContext имеет специальный обходной путь для клонирования стека операций CorrelationManager на клонирование контекста.К сожалению, он не имеет специального обходного пути для слияния.Идеальный!

PPS Образец:

static void Main(string[] args)
{
    string key = "aaa";
    EventWaitHandle asyncStarted = new AutoResetEvent(false);
    IAsyncResult r = null;

    CallContext.LogicalSetData(key, "Root - op 0");
    Console.WriteLine("Initial: {0}", CallContext.LogicalGetData(key));

    Action a = () =>
    {
        CallContext.LogicalSetData(key, "Async - op 0");
        asyncStarted.Set();
    };
    r = a.BeginInvoke(null, null);

    asyncStarted.WaitOne();
    Console.WriteLine("AsyncOp started: {0}", CallContext.LogicalGetData(key));

    CallContext.LogicalSetData(key, "Root - op 1");
    Console.WriteLine("Current changed: {0}", CallContext.LogicalGetData(key));

    a.EndInvoke(r);
    Console.WriteLine("Async ended: {0}", CallContext.LogicalGetData(key));

    Console.ReadKey();
}
...