如何安全地从EF的非同步SaveChanges中调用异步方法?

时间:2016-12-18 08:00:54

标签: c# entity-framework asynchronous async-await entity-framework-core

我使用的是ASP.NET Core,以及拥有SaveChangesSaveChangesAsync的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()中,如何在没有死锁的情况下安全地同步调用异步方法?

2 个答案:

答案 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

因此,最好将操作卸载到线程池,并避免死锁。