我使用的是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()
。
我总是&#34;一直向下同步#34;,但这里不可能。我可以重新设计LogAndAudit()
和LogAndAuditAsync()
,但这不是干的,我需要更改其他不属于我的主要代码。
关于这个主题还有很多其他问题,所有问题都是一般性的,复杂的,充满争议。 我需要知道在这个特定情况下最安全的方法。
那么,在SaveChanges()
中,如何在没有死锁的情况下安全地同步调用异步方法?
答案 0 :(得分:9)
从非异步方法调用异步方法的最简单方法是使用GetAwaiter().GetResult()
:
public override int SaveChanges {
LogAndAuditAsync().GetAwaiter().GetResult();
return base.SaveChanges();
}
这将确保LogAndAuditAsync
中引发的异常不会在AggregateException
中显示为SaveChanges
。而是传播原始异常。
但是,如果代码在执行异步同步(例如ASP.NET,Winforms和WPF)时可能会死锁的特殊同步上下文上执行,那么您必须更加小心。
每次LogAndAuditAsync
中的代码使用await
时,它都会等待任务完成。如果此任务必须在当前被LogAndAuditAsync().GetAwaiter().GetResult()
调用阻止的同步上下文上执行,则会出现死锁。
为避免这种情况,您需要将.ConfigureAwait(false)
添加到await
中的所有LogAndAuditAsync
来电。 E.g。
await file.WriteLineAsync(...).ConfigureAwait(false);
请注意,在此await
之后,代码将继续在同步上下文之外执行(在任务池调度程序上)。
如果不可能,您的最后一个选项是在任务池调度程序上启动新任务:
Task.Run(() => LogAndAuditAsync()).GetAwaiter().GetResult();
这仍将阻止同步上下文,但LogAndAuditAsync
将在任务池调度程序上执行而不是死锁,因为它不必输入被阻止的同步上下文。
答案 1 :(得分:0)
有许多方法可以进行同步异步,每个方法都有它的问题。但我需要知道哪个是最安全的特定用例。
答案是使用Stephen Cleary的"Thread Pool Hack":
Task.Run(() => LogAndAuditAsync()).GetAwaiter().GetResult();
原因是在该方法中,只执行了更多的数据库工作,没有别的。不需要原始的同步上下文 - 在EF Core的DbContext
中,您不需要访问ASP.NET Core的HttpContext
!
因此,最好将操作卸载到线程池,并避免死锁。