I'm using ASP.NET Core, and EF Core which has SaveChanges
and SaveChangesAsync
.
Before saving to the database, in my DbContext
, I perform some auditing/logging:
public async Task LogAndAuditAsync() {
// do async stuff
}
public override int SaveChanges {
/*await*/ LogAndAuditAsync(); // what do I do here???
return base.SaveChanges();
}
public override async Task<int> SaveChangesAsync {
await LogAndAuditAsync();
return await base.SaveChanges();
}
The problem is the synchronous SaveChanges()
.
I always do "async all the way down", but here that isn't possible. I could redesign to have LogAndAudit()
and LogAndAuditAsync()
but that is not DRY and I'll need to change a dozen other major pieces of code which don't belong to me.
There are lots of other questions about this topic, and all are general and complex and full of debate. I need to know the safest approach in this specific case.
So, in SaveChanges()
, how do I safely and synchronously call an async method, without deadlocks?
There are many ways to do sync-over-async, and each has it's gotchas. But I needed to know which is the safest for this specific use case.
The answer is to use Stephen Cleary's "Thread Pool Hack":
The reason is that within the method, only more database work is performed, nothing else. The original sychronization context is not needed - within EF Core's
DbContext
you shouldn't need access to ASP.NET Core'sHttpContext
!Hence it is best to offload the operation to the thread pool, and avoid deadlocks.