views:

389

answers:

4

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.

A: 

It's because you are mixing SetData/GetData with LogicalSetData/LogicalGetData. There's an article you could read more about differences between those two couple of methods. The rule of thumb here is to always use SetData in conjunction with GetData and LogicalSetData in conjunction with LogicalGetData.

This modification will make your test pass:

[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"));
}
Darin Dimitrov
@darin I've changed code example to use Logical methods. Sorry but this is not the point of my question. I can't uderstand what logic stays behind this strange behaviour. How can anyone want to override local data with those changed in other thread?
SeeR
If you want per-thread context storage you have to use SetData/GetData. Objects stored using LogicalSetData will flow across app domains even if they don't implement ILogicalThreadAffinative.
Darin Dimitrov
Problem is I want to capture and pass some data to the thread created by my main thread, but don't want to override it when this external thread finishes.Maybe there is other way, even if it's not my code that is calling BeginInvoke()/EndInvoke(). I'm open for suggestions.
SeeR
A: 

This is by design. Read the Async and CallContext section of Using CallContext

ShaneC
A: 

It is not by design, I have investigated, and I think it is a bug so I have submitted a report at https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=483006

Sean Hederman
A: 

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.

alexdej