10

私はSaveChangesSaveChangesAsyncを持つASP.NETコアとEFコアを使用しています。EFの非同期SaveChangesから非同期メソッドを安全に呼び出す方法を教えてください。

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()次のとおりです。

DbContextで、データベースに保存する前に、私はいくつかの監査/ロギングを実行します。

私はいつも「非同期でダウン」していますが、ここでは不可能です。私はLogAndAudit()LogAndAuditAsync()を持つように再設計することができますが、それはDRYではないので、私に属していない他の大部分のコードを変更する必要があります。

このトピックに関するその他の多くの質問があり、すべてが一般的で複雑であり、議論の余地があります。 この特定のケースで最も安全なアプローチを知る必要があります

したがって、SaveChanges()では、どのように安全かつ同期的に非同期メソッドをデッドロックなしで呼び出すことができますか?

+0

あなたはTask.Run(a)=> {...})を待っていますか? –

+0

@H.Herzlはいそうです。しかし、それをコンパイルしたり実行したりすることはできません。この特定のケースでは、デッドロックの可能性を避ける*最も安全な方法*が何であるかを知る必要があります。 sync-over-asyncを間違ってしまうのは簡単です。 – grokky

+0

問題を回避するために、ログ/監査にキューを使用することは可能ですか?ログ/監査操作は性質上非同期になりますので、非同期/待機は一切必要ありません。 – Menahem

答えて

0

sync-over-asyncを実行する方法はたくさんあり、それぞれには問題があります。しかし、この具体的なユースケースに最も安全なものはです。

答えは使用することがあるステファン・クリアリーさん"Thread Pool Hack"

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

理由は、メソッド内で、唯一のより多くのデータベース作業が行われていることである、何もありません。元の同期化コンテキストは必要ありません.EFコアのDbContextでは、ASP.NETコアのHttpContextにアクセスする必要はありません。

したがって、操作をスレッドプールにオフロードし、デッドロックを回避することをお勧めします。

9

非非同期メソッドから非同期メソッドを呼び出すための最も簡単な方法は、GetAwaiter().GetResult()を使用することです:

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

これはLogAndAuditAsyncにスローされた例外は、SaveChangesAggregateExceptionとして表示されないことを保証します。代わりに元の例外が伝播されます。

ただし、コードがSync-over-async(ASP.NET、Winforms、WPFなど)を行うときにデッドロックする特殊な同期コンテキストで実行されている場合は、さらに注意する必要があります。

LogAndAuditAsyncのコードは、awaitを使用するたびに、タスクが完了するのを待ちます。このタスクが、LogAndAuditAsync().GetAwaiter().GetResult()への呼び出しによって現在ブロックされている同期コンテキストで実行する必要がある場合は、デッドロックが発生します。

これを避けるには、.ConfigureAwait(false)をすべてawaitコールに追加する必要があります。これはLogAndAuditAsyncです。例えば。

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

このawait後のコードは、(タスクプールスケジューラに)同期コンテキスト外で実行し続けることに注意してください。

ことができない場合は、あなたの最後のオプションは、タスクプールスケジューラに新しいタスクを開始することです:

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

これはまだ同期コンテキストをブロックしますが、LogAndAuditAsyncはタスクプールスケジューラ上で実行し、デッドロックはありませんブロックされている同期コンテキストを入力する必要がないためです。

+0

私はこの構文を認識していますが、EFコアとASP.NETコアの永続性について、このユースケースで最も安全なアプローチはなぜですか? sync-over-asyncを行うにはいくつかの方法がありますが、これもその1つです。 – grokky

関連する問題