Open mortensp opened 3 months ago
Hi - I think the reason for this behaviour is explained here:
However, I'm not yet sure what the correct fix, is as CallContext
doesn't exist in .NET Core onwards. This will likely require a bit of research as to alternatives. We would need somewhere to store data that flows across async boundaries and also survives a reset of the ExecutionContext
...
Hi - Thanks for looking into this issue.
I do think that I have a working fix. My solution is to keep all the logic around the AsyncLocal and add a ThreadLocal as fallback. That is to:
I have tested this in WPF using Caliburn.Micro and its Async methods to Open new views/viewModels and that works fine. My only concern at present is what will happen in the continuation part of await, when this lands on a different thread. I do believe that the AsyncLocal will be present but I hasn't been able to test that yes,
Another idea is to change the object/class stored in the Async and ThreadLocal variables to an immutable struct. This should create a new copy on each reassignment and therefore automatic eliminate the need for parent chaining and restoring,
Best Regards - Morten
Here is my fix as off now. Note the conditional compilation symbol "MSPA" that has to be set in order to activate my fix. I could have made a Github branch, but i don't know how - sorry!
private static readonly AsyncLocal<DbContextScope?> AmbientDbContextScope = new();
#if MSPA
// Use ThreadLocal<> when AmbientDbContextScope is null
private static readonly ThreadLocal<DbContextScope?> ThreadLocalDbContextScope = new();
#endif
/// <summary>
/// Makes the provided 'dbContextScope' available as the the ambient scope via the AsyncLocal.
/// </summary>
internal static void SetAmbientScope(DbContextScope newAmbientScope)
{
ArgumentNullException.ThrowIfNull(newAmbientScope);
var current = AmbientDbContextScope.Value;
if (current == newAmbientScope)
return;
// Store the new scope in the AsyncLocal, making it the ambient scope
AmbientDbContextScope.Value = newAmbientScope;
#if MSPA
ThreadLocalDbContextScope.Value = AmbientDbContextScope.Value;
#endif
}
/// <summary>
/// Clears the ambient scope from the AsyncLocal.
/// </summary>
internal static void RemoveAmbientScope()
{
#if MSPA
if (ThreadLocalDbContextScope.Value == AmbientDbContextScope.Value)
ThreadLocalDbContextScope.Value = null;
#endif
AmbientDbContextScope.Value = null;
}
/// <summary>
/// Get the current ambient scope or null if no ambient scope has been setup.
/// </summary>
internal static DbContextScope? GetAmbientScope()
{
// Retrieve the ambient scope (if any)
#if MSPA
if (AmbientDbContextScope.Value is null)
return ThreadLocalDbContextScope.Value;
else
#endif
return AmbientDbContextScope.Value;
}
AmbientDbContextScope is reset by WPF after Window, Page etc. load. That didn't happen earlier when ThreadLocal was used!