Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

EndInvoke changes current CallContext - why?

I have following test

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

Can anyone tell me why this test is failing on last line?

Actually I know why - because EndInvoke is merging CallContext from paralell method to current one - but I don't understand the reason for this.

For me this behaviour is similiar to changing method parameter values from inside of method that is called :-(

EDIT: I've changed my code example to use only LogicalGetData and LogicalSetData. As you can see in my other question I want to pass some data to another thread but I didn't expected that EndInvoke() will override my values with those changed in other thread.

like image 455
SeeR Avatar asked May 19 '09 15:05

SeeR


1 Answers

The behavior illustrated by your example is indeed by design. The LogicalCallContext is able to flow bi-directionally through an async invocation or a .net remoting call. When you call EndInvoke, the child context's LogicalCallContext is merged back into the parent's, as you have observed. This is intentional, so that callers of remote methods can get access to any values set by the remote method. You can use this feature to flow data back from the child, if you'd like.

Debugging this with the help of the .NET Framework source stepping, there are explicit comments to this effect:

in 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);

in System.Runtime.Remoting.Proxies.RealProxy.EndInvokeHelper:

    // Merge the call context back into the thread that
    // called EndInvoke 
    CallContext.GetLogicalCallContext().Merge(
         mrm.LogicalCallContext);

If you want to avoid having the data merge, it's pretty easy to skip, just avoid calling EndInvoke from the main thread. You could for example use ThreadPool.QueueUserWorkItem, which will flow the LogicalCallContext in but not out, or call EndInvoke from an AsyncCallback.

Looking at the example on the Microsoft Connect site, the reason that you're not seeing the LogicalSetData value get flowed back from the RunWorkerCompleted call is that BackgroundWorker does not flow the context back. Also, remember that LogicalSetData is not the same as thread-local storage, so it doesn't matter that RunWorkerCompleted happens to be running on the UI thread -- the LogicalCallContext there is still a child context, and unless the parent explicitly flows it back by calling EndInvoke from the spawning thread, it will be abandoned. If you want thread-local storage, you can access that from Thread, like so:

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

This example pops a MessageBox displaying "blah!!". The reason is that both callbacks run on the UI thread, so have access to the same thread-local store.

Hope this helps clear things up.

like image 129
alexdej Avatar answered Sep 20 '22 10:09

alexdej