web-dev-qa-db-ja.com

EFデータコンテキスト-非同期/待機およびマルチスレッド

私は頻繁にasync/awaitを使用して、ASP.NET MVC Web APIスレッドが長時間実行されるI/Oおよびネットワーク操作、特にデータベース呼び出しによってブロックされないようにします。

System.Data.Entity名前空間は、FirstOrDefaultAsyncContainsAsyncCountAsyncなどのさまざまなヘルパー拡張機能を提供します。などなど。

ただし、データコンテキストはスレッドセーフではないため、次のコードに問題があることを意味します。

_var dbContext = new DbContext();
var something = await dbContext.someEntities.FirstOrDefaultAsync(e => e.Id == 1);
var morething = await dbContext.someEntities.FirstOrDefaultAsync(e => e.Id == 2);
_

実際、私は時々のような例外を見ています:

System.InvalidOperationException:接続は閉じられませんでした。接続の現在の状態は開いています。

正しいパターンは、データベースへのeach非同期呼び出しに別のusing(new DbContext...)ブロックを使用するのですか?その代わりに同期を実行する方が潜在的に有益ですか?

47
Alex

ここには膠着状態があります。 ASP.NET Web API実行環境のスレッドモデルを担当するAspNetSynchronizationContextは、awaitの後の非同期継続が同じスレッドで行われることを保証しません。これの全体的なアイデアは、ASP.NETアプリのスケーラビリティを高めることであり、ThreadPoolからのスレッドがブロックされて、保留中の同期操作がブロックされます。

ただし、 DataContextクラス(LINQ to SQLの一部) はスレッドセーフではないため、DataContext AP​​Iでスレッドの切り替えが発生する可能性がある場所では使用しないでください。呼び出します。非同期呼び出しごとに個別のusing構成体は、notヘルプのいずれかです。

_var something;
using (var dataContext = new DataContext())
{
    something = await dataContext.someEntities.FirstOrDefaultAsync(e => e.Id == 1);
}
_

これは、_DataContext.Dispose_がオブジェクトが最初に作成されたスレッドとは異なるスレッドで実行される可能性があり、これがDataContextが期待するものではないためです。

DataContext AP​​Iに固執したい場合、synchronouslyを呼び出すことが唯一の実行可能なオプションのようです。そのステートメントをEF API全体に拡張する必要があるかどうかはわかりませんが、DataContext AP​​Iで作成された子オブジェクトもおそらくスレッドセーフではないと思います。したがって、ASP.NETでは、usingスコープは、2つの隣接するawaitコール間のスコープに制限する必要があります。

await Task.Run(() => { /* do DataContext stuff here */ })を使用して、多数の同期DataContext呼び出しを別のスレッドにオフロードしたくなるかもしれません。ただし、それは 既知のアンチパターン になります。特に、要求を満たすために必要なスレッドの数を減らすことはないため、パフォーマンスとスケーラビリティのみを損なう可能性があるASP.NETのコンテキストでは。

残念ながら、ASP.NETの非同期アーキテクチャは優れていますが、一部の確立されたAPIやパターンとの互換性はありません(例: 同様のケース )。ここでは同時APIアクセスを扱っていないので、特に悲しいことです。つまり、複数のスレッドが同時にDataContextオブジェクトにアクセスしようとしないからです。

マイクロソフトがフレームワークの将来のバージョンでそれに対処することを願っています。

[UPDATE]しかし、大規模な場合、EFロジックを別のプロセス(WCFサービスとして実行)にオフロードして、 ASP.NETクライアントロジックに対するスレッドセーフな非同期API。このようなプロセスは、Node.jsと同様に、イベントマシンとしてカスタム同期コンテキストを使用して調整できます。 Node.jsのようなアパートメントのプールを実行することもでき、各アパートメントはEFオブジェクトに対するスレッドアフィニティを維持します。これにより、非同期EF APIの恩恵を引き続き受けることができます。

[UPDATE]これは、この問題の解決策を見つけるための 何らかの試み です。

31
noseratio

DataContext class は、LINQ to SQLの一部です。 async/await AFAIKを理解しないため、Entity Frameworkのasync拡張メソッドと共に使用しないでください。

DbContext class は、EF6以上を使用している限り、asyncで正常に機能します。ただし、一度に実行されるDbContextインスタンスごとに1つの操作(同期または非同期)のみを使用できます。コードで実際にDbContextを使用している場合は、例外の呼び出しスタックを調べて、同時使用を確認します(例:Task.WhenAll)。

すべてのアクセスがシーケンシャルであることが確実な場合は、最小限の再現を投稿するか、Microsoft Connectにバグとして報告してください。

58
Stephen Cleary