嵌套多线程操作跟踪

Sin*_*nix 4 .net multithreading trace operations

我的代码相似

void ExecuteTraced(Action a, string message)
{
    TraceOpStart(message);
    a();
    TraceOpEnd(message);
}
Run Code Online (Sandbox Code Playgroud)

回调(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包装成动态并在最后一个跟踪操作旁边调度所有调用.

Sin*_*nix 6

好的,我正在回答自己.

简短的一句:没有解决方案.

稍微详细一点:

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

CallContext.LogicalSetData()适合很好,但它合并值到原来的上下文结束异步操作(实际上,更换EndInvoke会调用之前所做的所有更改).从理论上讲,它甚至可能异步发生,从而产生CallContext.LogicalGetData()的不可预测的结果.

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

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是远程处理和远程处理的一部分已经过时了.

PS Thace.CorrelationManager在内部使用CallContext,因此也不能按预期工作.BTW,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();
}
Run Code Online (Sandbox Code Playgroud)