10

我使用的是ASP.NET Core,以及具有SaveChangesSaveChangesAsync的EF Core。如何從EF的非異步SaveChanges安全地調用異步方法?

之前保存到數據庫中,在我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(),我該如何安全地同步調用異步方法,而沒有死鎖?

+0

您是否嘗試過使用await Task.Run(()=> {...}); –

+0

@ H.Herzl是的,它的工作原理。但這不是我無法編譯或運行。這就是我需要知道的是,在這種特殊情況下,什麼是最安全的方式,避免了死鎖的可能性。非常容易讓sync-over-async錯誤。 – grokky

+0

避開這個問題,是否可以使用隊列進行日誌記錄/審計,然後日誌/審計操作本質上變得異步,所以您根本不需要異步/等待? – Menahem

回答

0

有很多方法可以執行sync-over-async,並且每個都有它的陷阱。但是我需要知道哪個對於這個特定用例最安全。

的答案是使用斯蒂芬·克利裏的"Thread Pool Hack"

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

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

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

9

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

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

這將確保在LogAndAuditAsync引發的異常沒有出現在SaveChangesAggregateException。而是傳播原始異常。

但是,如果代碼在執行異步同步時(例如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將執行的任務池調度,而不是死鎖因爲它不必輸入被阻止的同步上下文。

+0

我知道這個語法,但爲什麼它在這個用例中是最安全的方法,在EF Core和ASP.NET Core中是持久化的呢?有很多方法可以做同步異步,這是其中之一。 – grokky