1

I'm attempting to develop a library that implements the loosely-defined Ambient Context pattern. I need to consider both a high-degree of parallelism and remoting (.NET Framework 4.6.2). It seems I have 2 options available: AsyncLocal<T> and CallContext. I don't believe either satisfies both of those considerations.

Consider the following sample that demonstrates the remoting half of my issue:

class Program
{
    static string hello = "Hello from '{0}'! \n\tAsyncID: {1} \n\tContextID: {2} ";

    static void Main(string[] args)
    {
        OperationManager.CallContextOperationID = Guid.NewGuid();
        OperationManager.AsyncLocalOperationID = Guid.NewGuid();

        AppDomain other = AppDomain.CreateDomain("remote");
        SayHello();
        other.DoCallBack(SayHello);

        Console.ReadKey();
    }

    private static void SayHello()
    {
        string statement = string.Format(hello, AppDomain.CurrentDomain.FriendlyName,
            OperationManager.AsyncLocalOperationID,
            OperationManager.CallContextOperationID);
        Console.WriteLine(statement);
    }
}

internal class OperationManager
{
    private static string _slotName = "HelloWorld";
    private static readonly AsyncLocal<Guid> _operationID = new AsyncLocal<Guid>();

    public static Guid AsyncLocalOperationID
    {
        get => _operationID.Value;
        set => _operationID.Value = value;
    }

    public static Guid CallContextOperationID
    {
        get => (Guid)CallContext.LogicalGetData(_slotName);
        set => CallContext.LogicalSetData(_slotName, value);
    }
}

This class creates two GUIDs, storing one in an async local and the other in the logical call context. Then it spins up a new AppDomain, and prints the values from the current and remote domain.

Sample output from this program shows

Hello from 'ConsoleApp1.exe'!
    AsyncID: 4c9e7c3a-fef8-4948-b0f0-896abe7dc2dd
    ContextID: 4b479195-6fe8-43ae-a753-2fb3ccc57530
Hello from 'remote'!
    AsyncID: 00000000-0000-0000-0000-000000000000
    ContextID: 4b479195-6fe8-43ae-a753-2fb3ccc57530

And we can see that the CallContext made it across the remoting boundary, while the AsyncLocal did not (not that surprising).

The problem is that I don't believe I can leverage the CallContext in a "simple parallelism" environment (as laid out by Stephen Cleary in this post), due to the way that the CallContext is shared across threads. Stephen has an excellent pseudo-code example in the linked solution that I won't duplicate here. That example outlines the async portion of my issue.

My two options then become

AsyncLocal<T>: Works in a "simple parallelism" environment, but fails across remoting boundaries.

CallContext: Works across remoting boundaries, but fails in a "simple parallelism" environment.

Is there a third option here I am missing?

wohlstad
  • 12,661
  • 10
  • 26
  • 39
Matt
  • 1,674
  • 2
  • 16
  • 34

0 Answers0