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. IsAsyncLocalenough here? - Are there any other alternatives to
AsyncLocalandCallContext.LogicalGetData/CallContext.LogicalSetDatain .NET when it comes down to preserving values throughout the "logical code flow"?