.net - EndInvoke 更改当前 CallContext - 为什么?

标签 .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")); 
}

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

其实我知道为什么 - 因为 EndInvoke 正在将 CallContext 从并行方法合并到当前方法 - 但我不明白这样做的原因。

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

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

最佳答案

您的示例所说明的行为确实是设计使然。 LogicalCallContext 能够通过异步调用或 .net 远程调用双向流动。正如您所观察到的,当您调用 EndInvoke 时,子上下文的 LogicalCallContext 会合并回父上下文。这是有意的,以便远程方法的调用者可以访问远程方法设置的任何值。如果您愿意,您可以使用此功能将数据从 child 传回。

在 .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");
    }

本示例弹出一个 MessageBox,显示“blah!!”。原因是两个回调都在 UI 线程上运行,因此可以访问同一个线程本地存储。

希望这有助于澄清事情。

关于.net - EndInvoke 更改当前 CallContext - 为什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/883486/

相关文章:

c# - 使用链接文件的 App 或 Web Config 中的 AppSettings

c# - .Net Windows Forms ListView OwnerDraw LargeImageList 泄漏

multithreading - 为什么自旋锁在单个 CPU 上没有意义?

C:同时运行两个函数?

c# - ASP.NET Core 的最小 docker

c# - MVP 中的接口(interface)和类

c# - 对可能未打开或关闭的 SqlConnection 调用 Close

c# - 为什么逻辑调用上下文不跨线程传播?

c# - 如何在 C# 中捕获 Lua 异常

Android服务多线程和startForeground()