Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

LogicalCallContext flows across await in Console App but not VS UnitTest

I am using the Logical CallContext to flow information back across a series of awaits. Interestingly, in my test console app, everything works fine. However, when running my unit test in the context of a VS UnitTest, the call context does not seems to flow across the awaits.

Inside of the method: SendRequestAsyncImpl the call context is being set, and when I query the logical call context from a breakpoint at the moment the method returns, the call context is set just fine.

However, after the await returns in the below line:

Message response = await SendRequestAsyncImpl(m, true).ConfigureAwait(false);

The logical call context is empty. I thought maybe the problem would be fixed by setting ConfigureAwait(true) rather than false. But that does not fix the issue.

It doesn't matter what I try to flow, even setting a simple value type inside of SendRequestAsyncImpl like:

System.Runtime.Remoting.Messaging.CallContext.LogicalSetData("flag", true);

is not retrievable after the await.

Why is this working from my console app? But not from my unit test? What's different? (I've seen some other stack overflow questions that refer to AppDomain issues. But I can't even marshal a bool across the await. The ability to marshal the data doesn't appear to be the issue here.)

like image 571
Ayo I Avatar asked Feb 11 '16 20:02

Ayo I


1 Answers

So after reading Stephen Clearly's comments, both in this question and on the article: http://blog.stephencleary.com/2013/04/implicit-async-context-asynclocal.html the answer has become clear.

While in synchronous code the logical CallContext does flow back out of a method. In asynchronous methods the CallContext does not flow back out. This makes sense, since how would .NET know how I want the CallContext to be merged after something like Task.WhenAll(...). Executing the below code illustrates:

static void Main(string[] args)
{
    SynchronousCall();
    Task.WaitAll(Test(), Test2());
    var s = CallContext.LogicalGetData("SynchronousCall");
    var test = CallContext.LogicalGetData("Test");
    var test2 = CallContext.LogicalGetData("Test2");

    Console.WriteLine("s val: {0}", (s == null) ? "{null}" : s);
    Console.WriteLine("test val: {0}", (test == null) ? "{null}" : test);
    Console.WriteLine("test2 val: {0}", (test2 == null) ? "{null}" : test2);
}

private static void SynchronousCall()
{
    CallContext.LogicalSetData("SynchronousCall", true);
}

private static async Task<bool> Test()
{
    CallContext.LogicalSetData("Test", true);
    var b = await Task.Run<bool>(() => 
    {
        return true; 
    });
    return b;
}

private static async Task<bool> Test2()
{
    CallContext.LogicalSetData("Test2", true);
    var b = await Task.Run<bool>(() =>
    {
        return true;
    });
    return b;
}

Prints the following:

s val: True
test val: {null}
test2 val: {null}

So, as you can see, the synchronous method allows CallContext to flow out, but the asynchronous method does not.

I modified my method to inject a thread-safe collection into the CallContext prior to the awaitable method. I inject information into that collection, rather than directly into the CallContext. Since the upstream and downstream all obtain a reference to the same collection, this allows me to flow context up out of my awaited method by retrieving it from the collection after the awaited method returns.

Hopefully this helps someone else in the future.

like image 126
Ayo I Avatar answered Nov 03 '22 02:11

Ayo I