我的代码相似
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包装成动态并在最后一个跟踪操作旁边调度所有调用。
答案 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,甚至会失败,因为它设置CallContext.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();
}