如何從EF的非異步SaveChanges中安全地調用異步方法?

async-await asynchronous c# entity-framework 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()

我總是“一直異步”,但這是不可能的。我可以重新設計LogAndAudit()LogAndAuditAsync()但這不是DRY,我需要更改不屬於我的十幾個其他主要代碼。

關於這個主題還有很多其他問題,而且所有問題都是一般的,複雜的,充滿了爭議。 在這個具體案例中,我需要知道最安全的方法

那麼,在SaveChanges() ,如何在沒有死鎖的情況下安全地同步調用異步方法?

一般承認的答案

有許多方法可以進行同步異步,每個方法都有它的問題。但我需要知道哪個特定用例最安全

答案是使用Stephen Cleary的“Thread Pool Hack”

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

原因是在該方法中,只執行了更多的數據庫工作,沒有別的。不需要原始的同步上下文 - 在EF Core的DbContext您不需要訪問ASP.NET Core的HttpContext

因此,最好將操作卸載到線程池,並避免死鎖。


熱門答案

從非異步方法調用異步方法的最簡單方法是使用GetAwaiter().GetResult()

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

這將確保LogAndAuditAsync中拋出的異常不會在SaveChanges顯示為AggregateException 。而是傳播原始異常。

但是,如果代碼在特殊同步上下文上執行,在執行異步同步(例如ASP.NET,Winforms和WPF)時可能會死鎖,那麼您必須更加小心。

每次LogAndAuditAsync的代碼使用await它都會等待任務完成。如果此任務必須在當前被LogAndAuditAsync().GetAwaiter().GetResult()調用阻止的同步上下文上執行LogAndAuditAsync().GetAwaiter().GetResult()您有死鎖。

為避免這種情況,您需要將.ConfigureAwait(false)添加到LogAndAuditAsync所有await調用。例如

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

請注意,在此之後await代碼將繼續在同步上下文之外執行(在任務池調度程序上)。

如果這不可能,您的最後一個選項是在任務池調度程序上啟動新任務:

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

這仍然會阻止同步上下文,但LogAndAuditAsync將在任務池調度程序上執行而不是死鎖,因為它不必輸入被阻止的同步上下文。



Related

許可下: CC-BY-SA with attribution
不隸屬於 Stack Overflow
這個KB合法嗎? 是的,了解原因
許可下: CC-BY-SA with attribution
不隸屬於 Stack Overflow
這個KB合法嗎? 是的,了解原因