我正在使用 ASP.NET Core 和具有 SaveChanges
和 SaveChangesAsync
的 EF Core。
在保存到数据库之前,在我的 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();
}
问题是同步 SaveChanges()
。
我总是“一直异步”,但在这里这是不可能的。我可以重新设计以具有 LogAndAudit()
和 LogAndAuditAsync()
但这不是 DRY,我需要更改其他一些不属于的主要代码对我来说。
关于这个主题还有很多其他问题,而且都是笼统的、复杂的、充满争议的。 我需要知道在这种特定情况下最安全的方法。
那么,在 SaveChanges()
中,我该如何安全、同步地调用异步方法,而不会出现死锁?
最佳答案
从非异步方法调用异步方法的最简单方法是使用 GetAwaiter().GetResult()
:
public override int SaveChanges {
LogAndAuditAsync().GetAwaiter().GetResult();
return base.SaveChanges();
}
这将确保 LogAndAuditAsync
中抛出的异常不会在 SaveChanges
中显示为 AggregateException
。而是传播原始异常。
但是,如果代码在特殊的同步上下文中执行,在同步异步时可能会死锁(例如 ASP.NET、Winforms 和 WPF),那么您必须更加小心。
每次 LogAndAuditAsync
中的代码使用 await
时,它都会等待任务完成。如果此任务必须在当前被 LogAndAuditAsync().GetAwaiter().GetResult()
调用阻塞的同步上下文上执行,您就会遇到死锁。
为避免这种情况,您需要将 .ConfigureAwait(false)
添加到 LogAndAuditAsync
中的所有 await
调用。例如
await file.WriteLineAsync(...).ConfigureAwait(false);
请注意,在此 await
之后,代码将在同步上下文之外(在任务池调度程序上)继续执行。
如果这不可能,您最后的选择是在任务池调度程序上启动一个新任务:
Task.Run(() => LogAndAuditAsync()).GetAwaiter().GetResult();
这仍然会阻塞同步上下文,但 LogAndAuditAsync
将在任务池调度程序上执行,而不是死锁,因为它不必进入被阻塞的同步上下文。
关于c# - 如何从 EF 的非异步 SaveChanges 安全地调用异步方法?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41206510/