首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >EndInvoke改变当前的CallContext -为什么?

EndInvoke改变当前的CallContext -为什么?
EN

Stack Overflow用户
提问于 2009-05-19 15:32:50
回答 2查看 1.5K关注 0票数 7

我有下面的测试

代码语言:javascript
复制
[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。正如您在我的另一个问题中所看到的,我想将一些数据传递给另一个线程,但我没想到EndInvoke()会用其他线程中更改的值覆盖我的值。

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2009-09-24 16:33:33

你的例子所说明的行为确实是由设计造成的。LogicalCallContext能够双向地通过异步调用或.net远程调用进行流。当您调用EndInvoke时,子上下文的LogicalCallContext将被合并回父上下文,正如您所观察到的。这是有意的,因此远程方法的调用方可以访问由remote方法设置的任何值。如果您愿意的话,您可以使用此特性将数据从子节点返回。

在.NET框架源代码步进的帮助下调试这个过程,有一些显式的注释:

在System.Runtime.Remoting.Proxies.RemotingProxy.Invoke:中

代码语言:javascript
复制
    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:中

代码语言:javascript
复制
    // Merge the call context back into the thread that
    // called EndInvoke 
    CallContext.GetLogicalCallContext().Merge(
         mrm.LogicalCallContext);

如果您想避免数据合并,跳过非常容易,只需避免从主线程调用EndInvoke即可。例如,您可以使用ThreadPool.QueueUserWorkItem,它会流进LogicalCallContext,但不会输出,或者从AsyncCallback调用EndInvoke。

查看Microsoft站点上的示例,您看不到LogicalSetData值从RunWorkerCompleted调用返回的原因是BackgroundWorker没有返回上下文。另外,请记住,LogicalSetData与线程是不同的--本地存储,所以RunWorkerCompleted碰巧运行在UI线程上并不重要-- LogicalCallContext仍然有一个子上下文,除非父线程通过从生成线程调用EndInvoke显式地返回它,否则它将被放弃。如果您想要线程本地存储,可以从线程访问它,如下所示:

代码语言:javascript
复制
    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线程上运行,因此可以访问同一个线程本地存储。

希望这能把事情弄清楚。

票数 8
EN

Stack Overflow用户

发布于 2009-05-19 15:56:21

这是因为您将SetData/GetData与LogicalSetData/LogicalGetData混合在一起。有一个文章,您可以更多地了解这两种方法之间的差异。这里的经验法则是始终将SetData与GetData结合使用,LogicalSetData与LogicalGetData结合使用。

此修改将使您的测试通过:

代码语言:javascript
复制
[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"));
}
票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/883486

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档