Teilen über


Asynchrone Abfragen und Speichern

Hinweis

Nur EF6 und höher: Die Features, APIs usw., die auf dieser Seite erläutert werden, wurden in Entity Framework 6 eingeführt. Wenn Sie eine frühere Version verwenden, gelten manche Informationen nicht.

EF6 hat die Unterstützung für asynchrone Abfragen und Speichern mit den Schlüsselwörtern „async“ und „await“ eingeführt, die in .NET 4.5 eingeführt wurden. Obwohl nicht alle Anwendungen von Asynchronität profitieren können, kann sie verwendet werden, um die Reaktionsfähigkeit des Clients und die Serverskalierbarkeit beim Umgang mit lang ausgeführten Netzwerk- oder E/A-gebundenen Aufgaben zu verbessern.

Wann sollte Asynchronität wirklich verwendet werden?

Bei dieser exemplarischen Vorgehensweise werden die Konzepte der Asynchronität auf eine Weise eingeführt, durch die der Unterschied zwischen asynchroner und synchroner Programmausführung leicht zu erkennen ist. Es ist nicht die Absicht dieser exemplarischen Vorgehensweise, Schlüsselszenarien aufzuzeigen, in denen die asynchrone Programmierung Vorteile bietet.

Die asynchrone Programmierung konzentriert sich in erster Linie darauf, den aktuell verwalteten Thread (Thread mit .NET-Code) freizugeben, um andere Aufgaben auszuführen, während er auf einen Vorgang wartet, der keine Computezeit von einem verwalteten Thread erfordert. Während die Datenbank-Engine beispielsweise eine Abfrage verarbeitet, gibt es nichts, was von .NET-Code ausgeführt werden muss.

In Clientanwendungen (WinForms, WPF usw.) kann der aktuelle Thread verwendet werden, um die Benutzeroberfläche reaktionsfähig zu halten, während der asynchrone Vorgang ausgeführt wird. In Serveranwendungen (ASP.NET usw.) kann der Thread zum Verarbeiten anderer eingehender Anforderungen verwendet werden. Dies kann die Speicherauslastung reduzieren und/oder den Durchsatz des Servers erhöhen.

In den meisten Anwendungen wird das Verwenden von Asynchronität keine spürbaren Vorteile spürbar bringen und sich eventuell sogar nachteilig auswirken. Verwenden Sie Tests, Profilerstellung und Ihren gesunden Menschenverstand, um die Auswirkungen von Asynchronität in Ihrem jeweiligen Szenario zu beurteilen, bevor Sie sich dafür entscheiden.

Hier finden Sie einige weitere Ressourcen, um mehr über Asynchronität zu erfahren:

Erstellen des Modells

Wir verwenden den Code First-Workflow, um unser Modell zu erstellen und die Datenbank zu generieren. Die asynchrone Funktionalität funktioniert jedoch mit allen EF-Modellen, einschließlich denen, die mit dem EF Designer erstellt wurden.

  • Erstellen Sie eine neue Konsolenanwendung, und nennen Sie sie AsyncDemo.
  • Fügen Sie das EntityFramework-NuGet-Paket hinzu.
    • Klicken Sie im Projektmappen-Explorer mit der rechten Maustaste auf das AsyncDemo-Projekt.
    • Wählen Sie NuGet-Pakete verwalten... aus.
    • Wählen Sie im Dialogfeld „NuGet-Pakete verwalten“ die Registerkarte Online- und dann das EntityFramework-Paket aus.
    • Klicken Sie auf Install (Installieren).
  • Fügen Sie eine neue Model.cs-Klasse mithilfe der folgenden Implementierung hinzu:
    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; }
        }
    }

 

Erstellen eines synchronen Programms

Nachdem wir nun über ein EF-Modell verfügen, schreiben wir ein wenig Code, der es zum Ausführen eines Datenzugriffs verwendet.

  • Ersetzen Sie den Inhalt von Program.cs durch den folgenden Code:
    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);
                    }
                }
            }
        }
    }

Dieser Code ruft die PerformDatabaseOperations-Methode auf, die einen neuen Blog in der Datenbank speichert und dann alle Blogs aus der Datenbank abruft und sie in die Konsole druckt. Danach schreibt das Programm ein „Zitat des Tages“ in die Konsole.

Da der Code synchron ist, können wir beim Ausführen des Programms folgenden Ausführungsfluss beobachten:

  1. SaveChanges beginnt, den neuen Blog in die Datenbank zu pushen.
  2. SaveChanges wird beendet.
  3. Die Abfrage für alle Blogs wird an die Datenbank gesendet.
  4. Die Abfrage gibt Ergebnisse zurück, die in die Konsole geschrieben werden.
  5. Das „Zitat des Tages“ wird in die Konsole geschrieben.

Sync Output 

 

Das Programm asynchron machen

Nachdem unser Programm nun funktioniert, können wir mit der Nutzung der neuen Schlüsselwörter „async“ und „await“ beginnen. Wir haben die folgenden Änderungen an der Datei Programm.cs vorgenommen:

  1. Zeile 2: Die using-Anweisung für den System.Data.Entity-Namespace gewährt uns Zugriff auf die async-Erweiterungsmethoden von EF.
  2. Zeile 4: Die using-Anweisung für den System.Threading.Tasks-Namespace ermöglicht uns die Verwendung des Task-Typs.
  3. Zeilen 12 & 18: Wir erfassen als Aufgabe, die den Fortschritt von PerformSomeDatabaseOperations (Zeile 12) überwacht und dann die Programmausführung für diese Aufgabe blockiert, sobald das gesamte Programm abgeschlossen ist (Zeile 18).
  4. Zeile 25: Wir haben PerformSomeDatabaseOperations aktualisiert, sodass es als async gekennzeichnet ist und einen Taskzurückgibt.
  5. Zeile 35: Wir rufen nun die asynchrone Version von SaveChanges auf und warten auf den Abschluss.
  6. Zeile 42: Wir rufen nun die asynchrone Version von ToList auf und warten auf das Ergebnis.

Eine umfassende Liste der verfügbaren Erweiterungsmethoden im System.Data.Entity-Namespace finden Sie in der QueryableExtensions-Klasse. Sie müssen außerdem using System.Data.Entity zu Ihrer using-Anweisung hinzufügen.

    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);
                    }
                }
            }
        }
    }

Da der Code nun asynchron ist, können wir beim Ausführen des Programms den folgenden Ausführungsfluss beobachten:

  1. SaveChanges beginnt, den neuen Blog in die Datenbank zu pushen.
    Sobald der Befehl an die Datenbank gesendet wird, ist keine Computezeit für den aktuell verwalteten Thread erforderlich. Die PerformDatabaseOperations-Methode gibt ein Ergebnis zurück (auch wenn sie die Ausführung nicht abgeschlossen hat) und der Programmfluss in der Main-Methode läuft weiter.
  2. Das „Zitat des Tages“ wird in die Konsole geschrieben.
    Da es in der Main-Methode keine weiteren Aufgaben gibt, wird der verwaltete Thread für den Wait-Aufruf blockiert, bis der Datenbankvorgang abgeschlossen ist. Nach Abschluss des Vorgangs wird der Rest unserer PerformDatabaseOperations ausgeführt.
  3. SaveChanges wird beendet.
  4. Die Abfrage für alle Blogs wird an die Datenbank gesendet.
    Auch hier kann der verwaltete Thread andere Aufgaben ausführen, während die Abfrage in der Datenbank verarbeitet wird. Da die Ausführung ansonsten abgeschlossen ist, wird der Thread nur beim Wait-Aufruf angehalten.
  5. Die Abfrage gibt Ergebnisse zurück, die in die Konsole geschrieben werden.

Async Output 

 

Die Schlussfolgerung

Wir haben nun gesehen, wie einfach es ist, die asynchronen EF-Methoden zu verwenden. Obwohl die Vorteile von Asynchronität bei einer einfachen Konsolenanwendung möglicherweise nicht sehr offensichtlich sind, können dieselben Strategien in Situationen angewendet werden, in denen lange ausgeführte oder netzwerkgebundene Aktivitäten die Anwendung sonst blockieren könnten, oder eine große Anzahl von Threads den Speicherbedarf erhöhen würde.