Consider the following code:
private static async Task Main(string[] args)
{
await SetValueInAsyncMethod();
PrintValue();
await SetValueInNonAsyncMethod();
PrintValue();
}
private static readonly AsyncLocal<int> asyncLocal = new AsyncLocal<int>();
private static void PrintValue([CallerMemberName] string callingMemberName = "")
{
Console.WriteLine($"{callingMemberName}: {asyncLocal.Value}");
}
private static async Task SetValueInAsyncMethod()
{
asyncLocal.Value = 1;
PrintValue();
await Task.CompletedTask;
}
private static Task SetValueInNonAsyncMethod()
{
asyncLocal.Value = 2;
PrintValue();
return Task.CompletedTask;
}
If you run this code inside a .NET 4.7.2 console application, you will get the following output:
SetValueInAsyncMethod: 1
Main: 0
SetValueInNonAsyncMethod: 2
Main: 2
I do understand that the differences in the output arise from the fact that SetValueInAsyncMethod
is not really a method, but a state machine executed by AsyncTaskMethodBuilder
which captures ExecutionContext
internally and SetValueInNonAsyncMethod
is just a regular method.
But even with this understanding in mind I still have some questions:
- Is this a bug / missing feature or an intentional design decision?
- Do I need to worry about this behavior while writing code that depends on
AsyncLocal
? Say, I want to write myTransactionScope
-wannabe that flows some ambient data though await points. IsAsyncLocal
enough here? - Are there any other alternatives to
AsyncLocal
andCallContext.LogicalGetData
/CallContext.LogicalSetData
in .NET when it comes down to preserving values throughout the "logical code flow"?