web-dev-qa-db-ja.com

Entity Framework SaveChanges()vs. SaveChangesAsync()およびFind()vs. FindAsync()

上記の2つのペアの違いを探していましたが、それについて明確に説明している記事や、いつ使用するかについては見つかりませんでした。

SaveChanges()SaveChangesAsync()の違いは何ですか?
そしてFind()FindAsync()の間?

サーバー側では、Asyncメソッドを使用する場合、awaitも追加する必要があります。したがって、サーバー側では非同期だとは思いません。

クライアント側のブラウザーでUIのブロックを防ぐのに役立つだけですか?またはそれらの間に長所と短所はありますか?

70
Hien Tran

リモートサーバーでアクションを実行する必要があるときはいつでも、プログラムは要求を生成し、送信してから応答を待ちます。例としてSaveChanges()SaveChangesAsync()を使用しますが、Find()FindAsync()にも同じことが当てはまります。

データベースに追加する必要がある100以上のアイテムのリストmyListがあるとします。それを挿入するには、関数は次のようになります。

using(var context = new MyEDM())
{
    context.MyTable.AddRange(myList);
    context.SaveChanges();
}

最初にMyEDMのインスタンスを作成し、リストmyListをテーブルMyTableに追加してから、SaveChanges()を呼び出してデータベースへの変更を永続化します。希望どおりに機能し、レコードはコミットされますが、プログラムはコミットが完了するまで他に何もできません。これは、コミットする内容によっては時間がかかる場合があります。レコードへの変更をコミットする場合、エンティティはそれらを一度にコミットする必要があります(保存に更新に2分かかったことがあります)!

この問題を解決するには、次の2つのいずれかを実行できます。 1つ目は、挿入を処理する新しいスレッドを起動できることです。これにより、実行を継続するために呼び出しスレッドが解放されますが、そこに座って待機する新しいスレッドを作成しました。そのオーバーヘッドは不要であり、これがasync awaitパターンによって解決されます。

I/O操作の場合、awaitはすぐに親友になります。上記のコードセクションを使用して、次のように変更できます。

using(var context = new MyEDM())
{
    Console.WriteLine("Save Starting");
    context.MyTable.AddRange(myList);
    await context.SaveChangesAsync();
    Console.WriteLine("Save Complete");
}

非常に小さな変更ですが、コードの効率とパフォーマンスに大きな影響があります。それでどうなりますか?コードの始まりは同じです。MyEDMのインスタンスを作成し、myListMyTableに追加します。しかし、await context.SaveChangesAsync()を呼び出すと、コードの実行 呼び出し元の関数に戻ります! そのため、これらすべてのレコードがコミットされるのを待っている間、コードの実行を継続できます。上記のコードを含む関数の署名がpublic async Task SaveRecords(List<MyTable> saveList)であったとすると、呼び出し元の関数は次のようになります。

public async Task MyCallingFunction()
{
    Console.WriteLine("Function Starting");
    Task saveTask = SaveRecords(GenerateNewRecords());

    for(int i = 0; i < 1000; i++){
        Console.WriteLine("Continuing to execute!");
    }

    await saveTask;
    Console.Log("Function Complete");
}

なぜこのような関数を使用するのか、私にはわかりませんが、それが出力するものはasync awaitの仕組みを示しています。最初に何が起こるかを見てみましょう。

実行はMyCallingFunctionFunction Startingを入力し、Save Startingがコンソールに書き込まれ、次に関数SaveChangesAsync()が呼び出されます。この時点で、実行はMyCallingFunctionに戻り、 'Continuing to Execute'を最大1000回書き込むforループに入ります。 SaveChangesAsync()が終了すると、実行はSaveRecordsfunctionに戻り、Save Completeをコンソールに書き込みます。 SaveRecordsのすべてが完了すると、SaveChangesAsync()が終了したときにMyCallingFunctionで実行が続行されます。混乱した?出力例を次に示します。

関数の開始
保存の開始
実行を継続!
実行を継続!
実行を継続!
実行を継続! 
実行を継続!
 .... 
実行を継続!
保存を完了!
実行を継続!
継続
実行を継続!
 .... 
実行を継続!
機能完了!

または多分:

関数の開始
保存の開始
実行の継続!
実行の継続!
保存の完了!
実行の継続!
実行を継続!
実行を継続!
 .... 
実行を継続!
機能完了!

それがasync awaitの美しさです。何かが終了するのを待っている間、コードを実行し続けることができます。実際には、呼び出し関数として次のような関数があります。

public async Task MyCallingFunction()
{
    List<Task> myTasks = new List<Task>();
    myTasks.Add(SaveRecords(GenerateNewRecords()));
    myTasks.Add(SaveRecords2(GenerateNewRecords2()));
    myTasks.Add(SaveRecords3(GenerateNewRecords3()));
    myTasks.Add(SaveRecords4(GenerateNewRecords4()));

    await Task.WhenAll(myTasks.ToArray());
}

ここには、4つの異なるレコード保存機能があります 同時にMyCallingFunctionは、個々のSaveRecords関数が連続して呼び出された場合よりも、async awaitを使用するとはるかに高速に完了します。

まだ触れていないことの1つは、awaitキーワードです。これは、待機中のTaskが完了するまで、現在の関数の実行を停止します。したがって、元のMyCallingFunctionの場合、SaveRecords関数が終了するまでFunction Complete行はコンソールに書き込まれません。

要するに、async awaitを使用するオプションがある場合は、アプリケーションのパフォーマンスを大幅に向上させる必要があります。

144
Jacob Lambert

この文は正しくありません:

サーバー側で、Asyncメソッドを使用する場合、awaitを追加する必要もあります。

「待機」を追加する必要はありません。 "await"は、C#の便利なキーワードであり、呼び出し後にコードの行をさらに記述できます。これらの他の行は、保存操作が完了した後にのみ実行されます。しかし、指摘したように、SaveChangesAsyncの代わりにSaveChangesを呼び出すだけで、それを達成できます。

しかし基本的に、非同期呼び出しはそれ以上のものです。ここでの考え方は、保存操作の進行中に(サーバー上で)他の作業ができる場合、SaveChangesAsyncを使用する必要があるということです。 「待機」を使用しないでください。 SaveChangesAsyncを呼び出してから、他の処理を並行して続けます。これには、Webアプリで、保存が完了する前でもクライアントに応答を返す可能性があります。ただし、もちろん、保存の最終結果を確認して、失敗した場合にユーザーに通知するか、何らかの方法でログに記録することができます。

1
Rajeev Goel