非同期クエリと保存

Note

EF6 以降のみ - このページで説明する機能、API などは、Entity Framework 6 で導入されました。 以前のバージョンを使用している場合、一部またはすべての情報は適用されません。

EF6 では、.NET 4.5 で導入された async キーワードと await キーワードを使用して、非同期クエリと保存がサポートされるようになりました。 すべてのアプリケーションで非同期の恩恵を受けることができるわけではありませんが、長時間実行、ネットワーク、I/O バインドといったタスクを処理する際は、これを使用してクライアントの応答性とサーバーのスケーラビリティを向上させることができます。

実際に非同期を使用するタイミング

このチュートリアルの目的は非同期の概念を紹介することにあります。非同期および同期プログラムの実行の違いを簡単に観察できるような形でこれを示します。 このチュートリアルは、非同期プログラミングで利点が得られる主要シナリオについて説明することが目的ではありません。

非同期プログラミングでは、操作の待機中でマネージド スレッドのコンピューティング時間を必要としないときに、他の作業を行うことができるように現在のマネージド スレッド (.NET コードを実行しているスレッド) を解放することに主な重点が置かれています。 たとえば、データベース エンジンがクエリを処理している間は、.NET コードは何も実行していません。

クライアント アプリケーション (WinForms、WPF など) では、非同期操作の実行中に現在のスレッドを使って UI の応答性を維持できます。 サーバー アプリケーション (ASP.NET など) では、スレッドを使って他の受信要求を処理することができます。これにより、メモリ使用量を削減したり、サーバーのスループットを向上させたりすることができます。

ほとんどのアプリケーションでは、非同期を使用することに顕著な利点はなく、悪影響を及ぼす可能性さえあります。 コミットする前に、テストやプロファイルを使用し、また常識を働かせて、特定のシナリオでの非同期の影響を測定してください。

非同期については、次のリソースを参照してください。

モデルを作成する

ここでは、Code First ワークフローを使用してモデルを作成し、データベースを生成します。ただし、非同期機能は EF デザイナーで作成されたものを含め、すべての EF モデルで動作します。

  • コンソール アプリケーションを作成し、それに AsyncDemo という名前を付けます
  • EntityFramework NuGet パッケージを追加します
    • ソリューション エクスプローラーで AsyncDemo プロジェクトを右クリックします
    • [NuGet パッケージの管理] を選択します
    • [NuGet パッケージの管理] ダイアログで [オンライン] タブを選択し、[EntityFramework] パッケージを選択します。
    • [インストール]をクリックします。
  • 次の実装を使用して、Model.cs クラスを追加します
    using System.Collections.Generic;
    using System.Data.Entity;

    namespace AsyncDemo
    {
        public class BloggingContext : DbContext
        {
            public DbSet<Blog> Blogs { get; set; }
            public DbSet<Post> Posts { get; set; }
        }

        public class Blog
        {
            public int BlogId { get; set; }
            public string Name { get; set; }

            public virtual List<Post> Posts { get; set; }
        }

        public class Post
        {
            public int PostId { get; set; }
            public string Title { get; set; }
            public string Content { get; set; }

            public int BlogId { get; set; }
            public virtual Blog Blog { get; set; }
        }
    }

 

同期プログラムを作成する

EF モデルを用意できたので、これを使っていくつかのデータ アクセスを実行するコードを記述してみましょう。

  • Program.cs の内容を次のコードで置き換えます
    using System;
    using System.Linq;

    namespace AsyncDemo
    {
        class Program
        {
            static void Main(string[] args)
            {
                PerformDatabaseOperations();

                Console.WriteLine("Quote of the day");
                Console.WriteLine(" Don't worry about the world coming to an end today... ");
                Console.WriteLine(" It's already tomorrow in Australia.");

                Console.WriteLine();
                Console.WriteLine("Press any key to exit...");
                Console.ReadKey();
            }

            public static void PerformDatabaseOperations()
            {
                using (var db = new BloggingContext())
                {
                    // Create a new blog and save it
                    db.Blogs.Add(new Blog
                    {
                        Name = "Test Blog #" + (db.Blogs.Count() + 1)
                    });
                    Console.WriteLine("Calling SaveChanges.");
                    db.SaveChanges();
                    Console.WriteLine("SaveChanges completed.");

                    // Query for all blogs ordered by name
                    Console.WriteLine("Executing query.");
                    var blogs = (from b in db.Blogs
                                orderby b.Name
                                select b).ToList();

                    // Write all blogs out to Console
                    Console.WriteLine("Query completed with following results:");
                    foreach (var blog in blogs)
                    {
                        Console.WriteLine(" " + blog.Name);
                    }
                }
            }
        }
    }

このコードは、新しいブログをデータベースに保存し、データベースからすべてのブログを取得してコンソールに出力する PerformDatabaseOperations メソッドを呼び出します。 その後、このプログラムは今日の名言をコンソールに書き込みます。

コードは同期的であるため、プログラムを実行すると、次のような実行フローが観察できます。

  1. SaveChanges が、データベースへの新しいブログのプッシュを開始する
  2. SaveChanges が完了する
  3. すべてのブログを要求するクエリがデータベースに送信される
  4. クエリから結果が返され、コンソールに書き込まれる
  5. 今日の名言がコンソールに書き込まれる

Sync Output 

 

非同期にする

プログラムを起動して実行したので、新しい async キーワードと await キーワードの使用を開始できます。 ここでは、Program.cs に対して次の変更を行いました

  1. 2 行目: System.Data.Entity 名前空間の using ステートメントを使用すると、EF async 拡張メソッドにアクセスできます。
  2. 4 行目: System.Threading.Tasks 名前空間の using ステートメントを使用すると、Task 型を使用できます。
  3. 12 および 18 行目: PerformSomeDatabaseOperations の進行状況を監視するタスクとしてキャプチャを行い (12 行目)、プログラムのすべての作業が実行された後にこのタスクが完了するように、プログラム実行をブロックします (18 行目)。
  4. 25 行目: async としてマークされ、Task が返されるように PerformSomeDatabaseOperations を更新しました。
  5. 35 行目: SaveChanges の非同期バージョンを呼び出し、その完了を待機しています。
  6. 42 行目: ToList の非同期バージョンを呼び出し、結果を待機しています。

System.Data.Entity 名前空間で使用できる拡張メソッドの包括的な一覧については、QueryableExtensions クラスを参照してください。 "また、using ステートメントに using System.Data.Entity を追加する必要もあります。"

    using System;
    using System.Data.Entity;
    using System.Linq;
    using System.Threading.Tasks;

    namespace AsyncDemo
    {
        class Program
        {
            static void Main(string[] args)
            {
                var task = PerformDatabaseOperations();

                Console.WriteLine("Quote of the day");
                Console.WriteLine(" Don't worry about the world coming to an end today... ");
                Console.WriteLine(" It's already tomorrow in Australia.");

                task.Wait();

                Console.WriteLine();
                Console.WriteLine("Press any key to exit...");
                Console.ReadKey();
            }

            public static async Task PerformDatabaseOperations()
            {
                using (var db = new BloggingContext())
                {
                    // Create a new blog and save it
                    db.Blogs.Add(new Blog
                    {
                        Name = "Test Blog #" + (db.Blogs.Count() + 1)
                    });
                    Console.WriteLine("Calling SaveChanges.");
                    await db.SaveChangesAsync();
                    Console.WriteLine("SaveChanges completed.");

                    // Query for all blogs ordered by name
                    Console.WriteLine("Executing query.");
                    var blogs = await (from b in db.Blogs
                                orderby b.Name
                                select b).ToListAsync();

                    // Write all blogs out to Console
                    Console.WriteLine("Query completed with following results:");
                    foreach (var blog in blogs)
                    {
                        Console.WriteLine(" - " + blog.Name);
                    }
                }
            }
        }
    }

コードが非同期になったので、プログラムを実行すると、先ほどとは異なる実行フローが観察できます。

  1. SaveChanges が、データベースへの新しいブログのプッシュを開始する
    コマンドがデータベースに送信されると、現在のマネージド スレッドでは、もはやコンピューティング時間が必要ではなくなりました。 PerformDatabaseOperations メソッド (の実行が完了していなくても) から制御が返され、Main メソッドのプログラム フローが続行されます。
  2. 今日の名言がコンソールに書き込まれる
    Main メソッドで実行する作業がなくなったため、データベース操作が完了するまで、マネージド スレッドは Wait 呼び出しでブロックされます。 完了すると、PerformDatabaseOperations の残りの部分が実行されます。
  3. SaveChanges が完了する
  4. すべてのブログを要求するクエリがデータベースに送信される
    この場合も、データベースでクエリが処理されている間は、マネージド スレッドで他の作業を自由に行うことができます。 他のすべての実行が完了したため、スレッドは待機呼び出しで停止します。
  5. クエリから結果が返され、コンソールに書き込まれる

Async Output 

 

重要なポイント

EF の非同期メソッドを簡単に使用できることがわかりました。 単純なコンソール アプリでは非同期の利点があまり明確ではない場合もありますが、実行時間が長いアクティビティやネットワークにバインドされているアクティビティによってアプリケーションがブロックされたり、大量のスレッドがメモリ占有領域を増加させたりするような状況では、これと同じ戦略を適用できます。