简体   繁体   English

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

[英]How do I safely call an async method from EF's non-async SaveChanges?

I'm using ASP.NET Core, and EF Core which has SaveChanges and SaveChangesAsync . 我正在使用ASP.NET Core,以及具有SaveChangesSaveChangesAsync EF Core。

Before saving to the database, in my DbContext , I perform some auditing/logging: 在保存到数据库之前,在我的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();
}

The problem is the synchronous SaveChanges() . 问题是同步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. 我可以重新设计LogAndAudit()LogAndAuditAsync()但这不是DRY,我需要更改不属于我的十几个其他主要代码。

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? 那么,在SaveChanges() ,如何在没有死锁的情况下安全地同步调用异步方法?

The simplest way to call an async method from a non-async method is to use GetAwaiter().GetResult() : 从非异步方法调用异步方法的最简单方法是使用GetAwaiter().GetResult()

public override int SaveChanges {
    LogAndAuditAsync().GetAwaiter().GetResult();
    return base.SaveChanges();
}

This will ensure that an exception thrown in LogAndAuditAsync does not appear as an AggregateException in SaveChanges . 这将确保在抛出一个异常LogAndAuditAsync不会出现作为AggregateExceptionSaveChanges Instead the original exception is propagated. 而是传播原始异常。

However, if the code is executing on a special synchronization context that may deadlock when doing sync-over-async (eg ASP.NET, Winforms and WPF) then you have to be more careful. 但是,如果代码在特殊同步上下文上执行,在执行异步同步(例如ASP.NET,Winforms和WPF)时可能会死锁,那么您必须更加小心。

Every time the code in LogAndAuditAsync uses await it will wait for a task to complete. 每次LogAndAuditAsync的代码使用await它都会等待任务完成。 If this task has to execute on the synchronization context that currently is blocked by the call to LogAndAuditAsync().GetAwaiter().GetResult() you have a deadlock. 如果此任务必须在当前被LogAndAuditAsync().GetAwaiter().GetResult()调用阻止的同步上下文上执行LogAndAuditAsync().GetAwaiter().GetResult()您有死锁。

To avoid this you need to add .ConfigureAwait(false) to all await calls in LogAndAuditAsync . 为避免这种情况,您需要将.ConfigureAwait(false)添加到LogAndAuditAsync所有await调用。 Eg 例如

await file.WriteLineAsync(...).ConfigureAwait(false);

Notice that after this await the code will continue executing outside the synchronization context (on the task pool scheduler). 请注意,在此之后await代码将继续在同步上下文之外执行(在任务池调度程序上)。

If that is not possible your last option is to start a new task on the task pool scheduler: 如果这不可能,您的最后一个选项是在任务池调度程序上启动新任务:

Task.Run(() => LogAndAuditAsync()).GetAwaiter().GetResult();

This will still block the synchronization context but LogAndAuditAsync will execute on the task pool scheduler and not deadlock because it does not have to enter the synchronization context that is blocked. 这仍将阻止同步上下文,但LogAndAuditAsync将在任务池调度程序上执行而不是死锁,因为它不必输入被阻止的同步上下文。

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" : 答案是使用Stephen Cleary的“Thread Pool Hack”

Task.Run(() => LogAndAuditAsync()).GetAwaiter().GetResult();

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's HttpContext ! 不需要原始的同步上下文 - 在EF Core的DbContext您不需要访问ASP.NET Core的HttpContext

Hence it is best to offload the operation to the thread pool, and avoid deadlocks. 因此,最好将操作卸载到线程池,并避免死锁。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM