您的示例说明的行为确实是设计使然。 LogicalCallContext 能够通过异步调用或 .net 远程调用双向流动。如您所见,当您调用 EndInvoke 时,子上下文的 LogicalCallContext 将合并回父上下文。这是有意的,以便远程方法的调用者可以访问远程方法设置的任何值。如果您愿意,您可以使用此功能将数据从孩子那里返回。
在 .NET Framework 源代码步进的帮助下对此进行调试,有明确的 cmets 可以达到此效果:
在 System.Runtime.Remoting.Proxies.RemotingProxy.Invoke:
case Message.EndAsync:
// This will also merge back the call context
// onto the thread that called EndAsync
RealProxy.EndInvokeHelper(m, false);
在 System.Runtime.Remoting.Proxies.RealProxy.EndInvokeHelper 中:
// Merge the call context back into the thread that
// called EndInvoke
CallContext.GetLogicalCallContext().Merge(
mrm.LogicalCallContext);
如果您想避免数据合并,很容易跳过,只需避免从主线程调用 EndInvoke。例如,您可以使用 ThreadPool.QueueUserWorkItem,它将 LogicalCallContext in 流入但不会流出,或者从 AsyncCallback 调用 EndInvoke。
查看 Microsoft Connect 站点上的示例,您没有看到 LogicalSetData 值从 RunWorkerCompleted 调用流回的原因是 BackgroundWorker 没有将上下文流回。另外,请记住,LogicalSetData 与线程本地存储不同,因此 RunWorkerCompleted 恰好在 UI 线程上运行并不重要——LogicalCallContext 仍然有一个子上下文,除非父级显式将其流回通过从生成线程调用 EndInvoke,它将被放弃。如果您想要线程本地存储,您可以从 Thread 访问它,如下所示:
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
Thread.SetData(Thread.GetNamedDataSlot("foo"), "blah!!");
}
private void button1_Click(object sender, EventArgs e)
{
var val = (string)Thread.GetData(Thread.GetNamedDataSlot("foo"));
MessageBox.Show(val ?? "no value");
}
此示例弹出一个显示“blah!!”的 MessageBox。原因是两个回调都在 UI 线程上运行,因此可以访问同一个线程本地存储。
希望这有助于解决问题。