gro*_*kky 11 c# asynchronous entity-framework async-await entity-framework-core
我使用ASP.NET的核心,和EF核心,拥有SaveChanges和SaveChangesAsync.
在保存到数据库之前,在我的DbContext执行一些审计/日志记录中:
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();
}
Run Code Online (Sandbox Code Playgroud)
问题是同步SaveChanges().
我总是"一直异步",但这里不可能.我可以重新设计LogAndAudit(),LogAndAuditAsync()但这不是DRY,我需要更改十几个不属于我的主要代码.
关于这个主题还有很多其他问题,所有问题都是一般的,复杂的,充满争议.在这个具体案例中,我需要知道最安全的方法.
那么,在SaveChanges()没有死锁的情况下,如何安全地同步调用异步方法呢?
从非异步方法调用异步方法的最简单方法是使用GetAwaiter().GetResult():
public override int SaveChanges {
LogAndAuditAsync().GetAwaiter().GetResult();
return base.SaveChanges();
}
Run Code Online (Sandbox Code Playgroud)
这将确保抛出的异常LogAndAuditAsync不会显示为AggregateExceptionin SaveChanges.而是传播原始异常.
但是,如果代码在特殊同步上下文上执行,在执行异步同步(例如ASP.NET,Winforms和WPF)时可能会死锁,那么您必须更加小心.
每次LogAndAuditAsync使用代码时,await它都会等待任务完成.如果此任务必须在当前被调用阻止的同步上下文上执行,LogAndAuditAsync().GetAwaiter().GetResult()则会出现死锁.
为避免这种情况,您需要添加.ConfigureAwait(false)到所有await呼叫中LogAndAuditAsync.例如
await file.WriteLineAsync(...).ConfigureAwait(false);
Run Code Online (Sandbox Code Playgroud)
请注意,在此之后await,代码将继续在同步上下文之外执行(在任务池调度程序上).
如果这不可能,您的最后一个选项是在任务池调度程序上启动新任务:
Task.Run(() => LogAndAuditAsync()).GetAwaiter().GetResult();
Run Code Online (Sandbox Code Playgroud)
这仍将阻止同步上下文,但LogAndAuditAsync将在任务池调度程序上执行而不是死锁,因为它不必进入被阻止的同步上下文.
有很多方法可以实现异步同步,每种方法都有其缺陷。但我需要知道对于这个特定的用例来说哪个是最安全的。
答案是使用 Stephen Cleary 的“线程池黑客”:
Task.Run(() => LogAndAuditAsync()).GetAwaiter().GetResult();
Run Code Online (Sandbox Code Playgroud)
原因是在该方法内,只执行更多的数据库工作,没有执行其他任何操作。不需要原始的同步上下文 - 在 EF Core 中,DbContext您不需要访问 ASP.NET Core 的HttpContext!
因此,最好将操作卸载到线程池,并避免死锁。