EndInvoke更改当前的CallContext - 为什么?

See*_*eeR 6 .net multithreading

我有以下测试

[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")); 
}
Run Code Online (Sandbox Code Playgroud)

谁能告诉我为什么这个测试在最后一行失败?

其实我知道为什么 - 因为EndInvoke正在将paraConll方法的CallContext合并到当前的方法 - 但我不明白这个的原因.

对我来说,这种行为类似于从方法内部更改方法参数值:-(

编辑:我已经将我的代码示例更改为仅使用LogicalGetData和LogicalSetData.正如您在我的其他问题中所看到的,我想将一些数据传递给另一个线程,但我没想到EndInvoke()会覆盖我在其他线程中更改的值.

ale*_*dej 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);
Run Code Online (Sandbox Code Playgroud)

在System.Runtime.Remoting.Proxies.RealProxy.EndInvokeHelper中:

    // Merge the call context back into the thread that
    // called EndInvoke 
    CallContext.GetLogicalCallContext().Merge(
         mrm.LogicalCallContext);
Run Code Online (Sandbox Code Playgroud)

如果你想避免数据合并,那么很容易跳过,只是避免从主线程调用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");
    }
Run Code Online (Sandbox Code Playgroud)

这个例子弹出一个显示"blah !!"的MessageBox.原因是两个回调都在UI线程上运行,因此可以访问相同的线程本地存储.

希望这有助于澄清事情.