我有以下测试
[Test]
public void aaa()
{
CallContext.LogicalSetData("aa", "1");
Action parallelMethod = () => CallContext.LogicalSetData("aa", "2");
var r = parallelMethod.BeginInvoke(null, null);
parallelMethod.EndInvoke(r);
Assert.That(CallContext.LogicalGetData("aa"), Is.EqualTo("1"));
}
有人可以告诉我为什么这个测试在最后一行失败了吗?
其实我知道为什么 - 因为EndInvoke正在将CallContext从paralell方法合并到当前的方法 - 但我不明白这个的原因。
对我来说,这种行为类似于从方法内部更改方法参数值: - (
编辑:我已将我的代码示例更改为仅使用LogicalGetData和LogicalSetData。正如你在我的另一个question中看到的,我想将一些数据传递给另一个线程,但我没想到EndInvoke()会覆盖我在其他线程中更改的值。
答案 0 :(得分:7)
您的示例所示的行为确实是设计的。 LogicalCallContext能够通过异步调用或.net远程调用双向流动。当您调用EndInvoke时,正如您所观察到的那样,子上下文的LogicalCallContext将合并回父级。这是有意的,因此远程方法的调用者可以访问远程方法设置的任何值。如果您愿意,可以使用此功能从孩子那里流式传输返回。
在.NET Framework源步进的帮助下对此进行调试,对此效果有明确的注释:
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 但不会流出,或者从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线程上运行,因此可以访问相同的线程本地存储。
希望这有助于清理事情。
答案 1 :(得分:0)
这是因为您正在将SetData / GetData与LogicalSetData / LogicalGetData混合使用。有一个article你可以阅读更多关于这两种方法之间的差异。这里的经验法则是始终将SetData与GetData和LogicalSetData以及LogicalGetData结合使用。
此修改将使您的测试通过:
[Test]
public void aaa()
{
CallContext.SetData("aa", "1");
Action parallelMethod = () => CallContext.SetData("aa", "2");
var r = parallelMethod.BeginInvoke(null, null);
Assert.That(CallContext.GetData("aa"), Is.EqualTo("1"));
parallelMethod.EndInvoke(r);
Assert.That(CallContext.GetData("aa"), Is.EqualTo("1"));
}