Entity Framework SaveChanges()とSaveChangesAsync()およびFind()とFindAsync()


86

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

だから、何の違いであるSaveChanges()とはSaveChangesAsync()
そして、の間Find()FindAsync()

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

クライアント側のブラウザでUIがブロックされるのを防ぐのに役立つだけですか?それとも、それらの間に賛否両論がありますか?


2
非同期は、ずっとある非常に多くのクライアントアプリケーションでは、ブロッキングからクライアントUIスレッドを停止するよりも。まもなく専門家の回答が来ると確信しています。
jdphenix 2015

回答:


174

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

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

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

最初にのインスタンスを作成しMyEDM、リストmyListをテーブルに追加してからMyTable、を呼び出しSaveChanges()てデータベースへの変更を永続化します。それはあなたが望むように機能し、レコードはコミットされますが、プログラムはコミットが完了するまで他に何もできません。何をコミットしているかによっては、これには長い時間がかかる場合があります。レコードへの変更をコミットする場合、エンティティは一度に1つずつコミットする必要があります(私はかつて、更新に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と、あなたの追加myListにはMyTable。しかし、を呼び出すと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()呼び出されます。この時点で、実行はMyCallingFunctionforループに戻り、最大1000回まで「実行を継続」と書き込みます。ときSaveChangesAsync()に終了し、実行が戻るSaveRecordsの書き込み機能、Save Completeコンソールに。すべてがSaveRecords完了すると、実行は終了MyCallingFunction時の状態SaveChangesAsync()で続行されます。混乱していますか?出力例を次に示します。

機能開始
保存開始
実行を続けます!
実行を続けます!
実行を続けます!
実行を続けます!
実行を続けます!
...。
実行を続けます!
保存完了!
実行を続けます!
実行を続けます!
実行を続けます!
...。
実行を続けます!
機能完了!

または多分:

機能開始
保存開始
実行を続けます!
実行を続けます!
保存完了!
実行を続けます!
実行を続けます!
実行を続けます!
...。
実行を続けます!
機能完了!

それがの美しさです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を使用するとはるかに速く完了します。async awaitSaveRecords

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

簡単に言うと、使用するオプションがある場合async awaitは、アプリケーションのパフォーマンスが大幅に向上するため、使用する必要があります。


7
99%の時間、続行する前にデータベースから値が受信されるのを待つ必要があります。まだ非同期を使用する必要がありますか?asyncでは100人が私のウェブサイトに非同期で接続できますか?asyncを使用しない場合、100人のユーザー全員が一度に1行目で待機する必要があるということですか?
マイク2015

6
注目に値する:スレッドプールから新しいスレッドを生成すると、基本的にASPからスレッドを奪うため、ASPは悲しいパンダになります(つまり、スレッドはブロッキング呼び出しでスタックしているため、他の要求を処理したり、何もできません)。awaitただし、使用する場合、SaveChangesの呼び出し後に他に何もする必要がない場合でも、ASPは「ああ、このスレッドは非同期操作を待って戻ってきました。これは、このスレッドに他の要求を処理させることができることを意味します。 !」これにより、アプリの水平方向のスケーリングが大幅に向上します。
サラ2016年

3
実際、私は非同期をより遅くなるようにベンチマークしました。また、一般的なASP.Netサーバーで使用できるスレッドの数を見たことがありますか?それは数万のようなものです。したがって、それ以上の要求を処理するためにスレッドが不足する可能性はほとんどありません。それらすべてのスレッドを飽和させるのに十分なトラフィックがあったとしても、サーバーはその場合に座屈しないほど強力ですか?どこでも非同期を使用するとパフォーマンスが向上すると主張するのは完全に間違っています。特定のシナリオでは可能ですが、ほとんどの一般的な状況では、実際には遅くなります。ベンチマークと参照してください。
user3766657 2016

@MIKE 1人のユーザーはデータベースがデータを返すのを待つ必要がありますが、アプリケーションを使用している他のユーザーはそうしません。IISはリクエストごとにスレッドを作成しますが(実際にはそれよりも複雑です)、待機中のスレッドを使用して他のリクエストを処理できます。これはスケーラビリティの観点から重要です。各リクエストをイメージングすると、1つのスレッドをフルタイムで使用する代わりに、別の場所(別のリクエスト)で再利用できる多くの短いスレッドが使用されます。
Bart Calixto 2016年

1
EFは同時に複数の保存をサポートしていないため、常に注意 する必要があることを付け加えておきます。 docs.microsoft.com/en-us/ef/core/saving/async また、これらの非同期メソッドを使用することには、実際には大きな利点があります。たとえば、データを保存したり、多くの作業を行ったりするときにwebApiで他のリクエストを受信し続けることができます。また、デスクトップアプリケーションを使用しているときに、インターフェイスがフリーズしないようにユーザーエクスペリエンスを向上させることができます。awaitSaveChangesAsync
tgarcia

1

私の残りの説明は、次のコードスニペットに基づいています。

using System;
using System.Threading;
using System.Threading.Tasks;
using static System.Console;

public static class Program
{
    const int N = 20;
    static readonly object obj = new object();
    static int counter;

    public static void Job(ConsoleColor color, int multiplier = 1)
    {
        for (long i = 0; i < N * multiplier; i++)
        {
            lock (obj)
            {
                counter++;
                ForegroundColor = color;
                Write($"{Thread.CurrentThread.ManagedThreadId}");
                if (counter % N == 0) WriteLine();
                ResetColor();
            }
            Thread.Sleep(N);
        }
    }

    static async Task JobAsync()
    {
       // intentionally removed
    }

    public static async Task Main()
    {
       // intentionally removed
    }
}

ケース1

static async Task JobAsync()
{
    Task t = Task.Run(() => Job(ConsoleColor.Red, 1));
    Job(ConsoleColor.Green, 2);
    await t;
    Job(ConsoleColor.Blue, 1);
}

public static async Task Main()
{
    Task t = JobAsync();
    Job(ConsoleColor.White, 1);
    await t;
}

ここに画像の説明を入力してください

備考:JobAsyncスピンの同期部分(緑)がタスクt(赤)よりも長いため、タスクtはの時点ですでに完了していawait tます。その結果、継続(青)は緑のスレッドと同じスレッドで実行されます。Main(白)の同期部分は、緑の同期部分の回転が終了した後に回転します。そのため、非同期方式の同期部分に問題があります。

ケース2

static async Task JobAsync()
{
    Task t = Task.Run(() => Job(ConsoleColor.Red, 2));
    Job(ConsoleColor.Green, 1);
    await t;
    Job(ConsoleColor.Blue, 1);
}

public static async Task Main()
{
    Task t = JobAsync();
    Job(ConsoleColor.White, 1);
    await t;
}

ここに画像の説明を入力してください

備考:この場合は、最初の場合とは逆です。JobAsyncタスクt(赤)よりも短いスピンの同期部分(緑)はt、の時点でタスクが完了していませんawait t。その結果、継続(青)は緑のスレッドとは異なるスレッドで実行されます。Main(白)の同期部分は、緑の同期部分の回転が終了した後も回転します。

ケース3

static async Task JobAsync()
{
    Task t = Task.Run(() => Job(ConsoleColor.Red, 1));
    await t;
    Job(ConsoleColor.Green, 1);
    Job(ConsoleColor.Blue, 1);
}

public static async Task Main()
{
    Task t = JobAsync();
    Job(ConsoleColor.White, 1);
    await t;
}

ここに画像の説明を入力してください

備考:このケースは、非同期方式の同期部分に関する以前のケースの問題を解決します。タスクtはすぐに待機されます。その結果、継続(青)は緑のスレッドとは異なるスレッドで実行されます。Main(白)の同期部分は、に平行にすぐに回転しJobAsyncます。

他のケースを追加したい場合は、自由に編集してください。


1

このステートメントは正しくありません:

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

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

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


4
実際にはこれらの呼び出しを待ちたいと思います。そうしないと、同じDbContextインスタンスを使用してクエリを実行したり、データを同時に保存したりする可能性があり、DbContextはスレッドセーフではありません。その上、待機により、例外の処理が容易になります。待つことなく、タスクを保存して障害が発生していないかどうかを確認する必要がありますが、タスクがいつ完了したかがわからない場合は、待つよりもはるかに多くの考慮が必要な「.ContinueWith」を使用しない限り、いつ確認するかわかりません。
Pawel 2015

22
この答えは欺瞞的です。待たずに非同期メソッドを呼び出すと、「ファイアアンドフォーゲット」になります。メソッドはオフになり、おそらくいつか完了するでしょうが、いつかはわかりません。また、例外がスローされた場合は、そのことを聞くことはありません。その完了と同期することはできません。この種の潜在的に危険な動作を選択する必要があります。「クライアントで待機し、サーバーで待機しない」などの単純な(そして誤った)ルールで呼び出さないでください。
ジョンメルビル2015年

1
これは私がドキュメントで読んだ非常に有用な知識ですが、実際には考慮していませんでした。したがって、次のオプションがあります。1。John Melvilleが言うように、SaveChangesAsync()を「Fireandforget」に設定します...これは場合によっては便利です。2. SaveChangesAsync()が「起動し、呼び出し元に戻り、保存が完了した後に「保存後」コードを実行するのを
待ち
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.