好吧,我自己回答。
短一:没有解决办法。
稍微详细一点:
问题是,我需要一种方法来存储每个逻辑上下文的最后一个活动操作。跟踪代码无法控制执行流程,因此不可能将 lastStartedOperation 作为参数传递。调用上下文可能会克隆(例如,如果另一个线程启动),因此我需要将值克隆为上下文克隆。
CallContext.LogicalSetData() 很适合,但它会在异步操作结束时将值合并到原始上下文中(实际上,替换调用 EndInvoke 之前所做的所有更改)。从理论上讲,它may甚至异步发生,导致 CallContext.LogicalGetData() 产生不可预测的结果。
我说理论上是因为 asyncCallback 内的简单调用 a.EndInvoke() 不会替换原始上下文中的值。不过,我没有检查远程调用的行为(看起来,WCF 根本不尊重 CallContext)。另外,文档 http://msdn.microsoft.com/en-us/library/w61s16a1(VS.71).aspx(旧的)说:
BeginInvoke 方法传递
CallContext 到服务器。什么时候
调用EndInvoke方法时,
CallContext 被合并回
线。这包括以下情况
调用 BeginInvoke 和 EndInvoke
依次和其中 BeginInvoke 是
在一个线程上调用,EndInvoke 是
调用回调函数。
最后一个版本不太明确:
BeginInvoke 方法传递
CallContext 到服务器。当。。。的时候
调用EndInvoke方法,获取数据
CallContext 中包含的内容是复制的
回到调用的线程
开始调用。
如果您深入研究框架源代码,您会发现值实际上存储在当前线程的当前 ExecutionContext 内的 LogicalCallContext 内的哈希表中。
当调用上下文克隆时(例如在 BeginInvoke 上),调用 LogicalCallContext.Clone。 EndInvoke(至少在原始 CallContext 中调用时)调用 LogicalCallContext.Merge(),用新值替换 m_Datastore 中的旧值。
因此,我们需要以某种方式提供将被克隆但不会合并回来的值。
LogicalCallContext.Clone() 还克隆(不合并)两个私有字段 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 字段的值。它只是用 null 替换旧值。
因此,没有解决方案,也永远不会有解决方案,因为 CallContext 是远程处理的一部分,而远程处理已经过时了。
附: Thace.CorrelationManager 在内部使用 CallContext,因此也无法按预期工作。顺便说一句,LogicalCallContext 有特殊的解决方法来在上下文克隆上克隆 CorrelationManager 的操作堆栈。遗憾的是,它没有关于合并的特殊解决方法。完美的!
附言例子:
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();
}