我有一个类似的代码

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 包装成动态并调度除上次跟踪操作之外的所有调用。

最佳答案

好的,我自己回答。

简短一: 没有解决办法。

稍微详细一点:

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

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

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



上一个版本不是那么明确:



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

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

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

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

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

您不能从 LogicalCallContext 继承 - 它是密封的。 (注意,实际上,您可以 - 如果使用 CLR 分析 API 来替换模拟框架所做的 IL。不是理想的解决方案。)

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

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

如果使用 HttpContext,即使这也会失败,因为它会设置 CallContext.HostContext 属性来替换您的旧值。具有讽刺意味的是,HttpContext 没有实现 ILogicalThreadAffinative,因此没有存储为 m_hostContext 字段的值。它只是用空值替换旧值。

因此,没有解决方案,也永远不会,因为 CallContext 是远程处理的一部分,而远程处理已过时。

附言Thace.CorrelationManager 在内部使用 CallContext,因此也无法按预期工作。顺便说一句,LogicalCallContext 有特殊的解决方法来在上下文克隆上克隆 CorrelationManager 的操作堆栈。遗憾的是,它在合并方面没有特殊的解决方法。完美的!

P.P.S. sample :

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

关于.net - 嵌套多线程操作跟踪,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/2651327/

10-10 07:55