嵌套多线程操作跟踪

时间:2010-04-16 07:32:18

标签: .net multithreading tracing operations

我的代码相似

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 changes current CallContext - why?

此外,还有Trace.CorrelationManager,但它基于CallContext并且具有所有相同的麻烦。

有一种解决方法:使用CallContext.HostContext属性,该属性在异步操作结束时不会传播回来。此外,它没有克隆,所以值应该是不可变的 - 不是问题。但是,它被HttpContext使用,因此,解决方法在Asp.Net应用程序中不可用。

我看到的唯一方法是将HostContext(如果不是我的)或整个LogicalCallContext包装成动态并在最后一个跟踪操作旁边调度所有调用。

1 个答案:

答案 0 :(得分:6)

好的,我正在回答自己。

短篇:没有解决方案。

稍微详细一点:

问题是,我需要一种方法来存储每个逻辑上下文的最后一个活动操作。跟踪代码无法控制执行流程,因此无法将lastStartedOperation作为参数传递。调用上下文可以克隆(例如,如果另一个线程启动),所以我需要将值克隆为上下文克隆。

CallContext.LogicalSetData()非常适合,但它会在异步操作结束时将值合并到原始上下文中(实际上,替换在调用EndInvoke之前所做的所有更改)。理论上,可能甚至异步发生,给出CallContext.LogicalGetData()的不可预知的结果。

我在理论上说,因为在asyncCallback中简单调用a.EndInvoke()不会替换原始上下文中的值。虽然,我没有检查远程调用的行为(似乎,WCF根本不尊重CallContext)。此外,documentation(旧的)说:

  

BeginInvoke方法传递   CallContext到服务器。什么时候   调用EndInvoke方法,   CallContext被合并回到   线。这包括的情况   调用BeginInvoke和EndInvoke   顺序和 BeginInvoke所在的位置   调用一个线程,EndInvoke是   呼叫回调函数。

最后版本不是那么明确:

  

BeginInvoke方法传递   CallContext到服务器。当。。。的时候   调用EndInvoke方法,即数据   CallContext中包含的内容是复制的   回到调用的线程   的BeginInvoke。

如果您深入了解框架源代码,您会发现这些值实际存储在当前线程的当前ExecutionContext中的LogicalCallContext内的哈希表中。

调用上下文克隆(例如,在BeginInvoke上)时调用LogicalCallContext.Clone。 EndInvoke(至少在原始CallContext中调用时)调用LogicalCallContext.Merge()用新的值替换m_Datastore中的旧值。

所以我们需要以某种方式提供将被克隆但不会合并的价值。

LogicalCallContext.Clone()还克隆(不合并)两个私有字段m_RemotingData和m_SecurityData的内容。由于字段的类型定义为内部,您无法从它们派生(即使使用emit),添加属性MyNoFlowbackValue并将m_RemotingData(或另一个)字段的值替换为派生类的实例。

此外,字段的类型不是从MBR派生的,因此不可能使用透明代理来包装它们。

你无法从LogicalCallContext继承 - 它是密封的。 (实际上,你可以 - 如果使用CLR profiling api来替换IL作为模拟框架那么做。不是一个理想的解决方案。)

您无法替换m_Datastore值,因为LogicalCallContext仅序列化哈希表的内容,而不是哈希表本身。

最后的解决方案是使用CallContext.HostContext。这有效地将数据存储在LogicalCallContext的m_hostContext字段中。 LogicalCallContext.Clone()共享(而不是克隆)m_hostContext的值,因此该值应该是不可变的。不过不是问题。

如果使用HttpContext,甚至会失败,因为它设置Call​​Context.HostContext属性来替换旧值。具有讽刺意味的是,HttpContext没有实现ILogicalThreadAffinative,因此不会存储为m_hostContext字段的值。它只是将旧值替换为null。

所以,没有解决方案也永远不会,因为CallContext是远程处理和远程处理的一部分已经过时了。

P.S。 Thace.CorrelationManager在内部使用CallContext,因此也不能按预期工作。 BTW,LogicalCallContext有特殊的解决方法来克隆上下文克隆上的CorrelationManager操作堆栈。可悲的是,它没有关于合并的特殊解决方法。完美!

P.P.S。样本:

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();
}