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?