Consulta asincrónica y guardado

Nota:

Solo EF6 y versiones posteriores: las características, las API, etc. que se tratan en esta página se han incluido a partir de Entity Framework 6. Si usa una versión anterior, no se aplica parte o la totalidad de la información.

EF6 introdujo soporte para consultas asincrónicas y guardado usando las palabras clave asincrónico y esperar que se introdujeron en .NET 4.5. Aunque no todas las aplicaciones pueden beneficiarse de la asincronía, se puede usar para mejorar la capacidad de respuesta del cliente y la escalabilidad del servidor al controlar tareas enlazadas a E/S o de larga duración.

Cuándo usar realmente asincrónico

El propósito de este tutorial es presentar los conceptos asíncronos de una manera que facilite observar la diferencia entre la ejecución de programas asíncronos y síncronos. Este tutorial no está pensado para ilustrar ninguno de los escenarios clave en los que la programación asincrónica proporciona ventajas.

La programación asincrónica se centra principalmente en liberar el subproceso administrado actual (subproceso que ejecuta código .NET) para realizar otro trabajo mientras espera una operación que no requiere ningún tiempo de proceso desde un subproceso administrado. Por ejemplo, mientras que el motor de base de datos está procesando una consulta no hay nada que haga el código de .NET.

En las aplicaciones cliente (WinForms, WPF, etc.), el subproceso actual se puede usar para mantener la interfaz de usuario con capacidad de respuesta mientras se realiza la operación asincrónica. En las aplicaciones de servidor (ASP.NET etc.), el subproceso se puede usar para procesar otras solicitudes entrantes; esto puede reducir el uso de memoria o aumentar el rendimiento del servidor.

En la mayoría de las aplicaciones, el uso de asincrónico no tendrá beneficios notables e incluso podría ser perjudicial. Use pruebas, generación de perfiles y sentido común para medir el impacto de asincrónico en su escenario concreto antes de confirmarlo.

Estos son algunos recursos más para obtener información sobre asincrónico:

Creación del modelo

Se usará el flujo de trabajo de Code First para crear nuestro modelo y generar la base de datos, pero la funcionalidad asincrónica funcionará con todos los modelos de EF, incluidos los creados con EF Designer.

  • Cree una aplicación de consola y llámela AsyncDemo
  • Agregar del paquete de NuGet EntityFramework
    • En el Explorador de soluciones, haga clic con el botón derecho en el proyecto AsyncDemo
    • Seleccione Administrar paquetes NuGet…
    • En el cuadro de diálogo Administrar paquetes NuGet, seleccione la pestaña En línea y elija el paquete EntityFramework
    • Haz clic en Instalar
  • Agregue una clase Model.cs con la siguiente implementación
    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; }
        }
    }

 

Crear un programa sincrónico

Ahora que tenemos un modelo de EF, vamos a escribir código que lo use para realizar algunos accesos a datos.

  • Reemplace el contenido de Program.cs por el código siguiente
    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);
                    }
                }
            }
        }
    }

Este código llama al método PerformDatabaseOperations que guarda un nuevo Blog en la base de datos y luego recupera todos los Blogs de la base de datos y los imprime en la Consola. Después de esto, el programa escribe una cita del día en la Consola.

Dado que el código es sincrónico, podemos observar el siguiente flujo de ejecución cuando se ejecuta el programa:

  1. SaveChanges comienza a insertar el nuevo Blog en la base de datos
  2. SaveChanges completa
  3. La consulta de todos los Blogs se envía a la base de datos
  4. La consulta devuelve y los resultados se escriben en Consola
  5. La cita del día se escribe en Consola

Sync Output 

 

Hacer que sea asincrónico

Ahora que tenemos nuestro programa en funcionamiento, podemos empezar a usar las nuevas palabras clave asincrónicas y await. Se han realizado los siguientes cambios en Program.cs

  1. Línea 2: la instrucción using para el espacio de nombres System.Data.Entity proporciona acceso a los métodos de extensión asincrónica de EF.
  2. Línea 4: la instrucción using del espacio de nombres System.Threading.Tasks permite usar el tipo Task.
  3. Línea 12 y 18: se está capturando como tarea que supervisa el progreso de PerformSomeDatabaseOperations (línea 12) y, a continuación, bloqueando la ejecución del programa para que esta tarea se complete una vez completado todo el trabajo del programa (línea 18).
  4. Línea 25: se ha actualizado PerformSomeDatabaseOperations para que se marque como async y que devuelva un Task.
  5. Línea 35: ahora se está llamando a la versión asincrónica de SaveChanges y esperando su finalización.
  6. Línea 42: ahora se está llamando a la versión asincrónica de ToList y a la espera del resultado.

Para obtener una lista completa de los métodos de extensión disponibles en el espacio de nombres System.Data.Entity, consulte la clase QueryableExtensions. También deberá agregar using System.Data.Entity a las instrucciones using.

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

Ahora que el código es asincrónico, podemos observar un flujo de ejecución diferente al ejecutar el programa:

  1. SaveChanges comienza a insertar el nuevo Blog en la base de datos
    Una vez que el comando se envía a la base de datos, no se necesita más tiempo de proceso en el subproceso administrado actual. El método PerformDatabaseOperations devuelve (aunque no haya terminado de ejecutarse) y el flujo de programa en el método Main continúa.
  2. La cita del día se escribe en la consola
    Puesto que no hay más trabajo que hacer en el método Main, el subproceso administrado se bloquea en la llamada Wait hasta que se complete la operación de base de datos. Una vez completado, se ejecutará el resto de PerformDatabaseOperations.
  3. SaveChanges completa
  4. La consulta de todos los Blogs se envía a la base de datos
    De nuevo, el subproceso administrado es libre de realizar otro trabajo mientras la consulta se procesa en la base de datos. Sin embargo, dado que todas las demás ejecuciones se han completado, el hilo simplemente se detendrá en la llamada de espera.
  5. La consulta devuelve y los resultados se escriben en Consola

Async Output 

 

El resumen

Ahora vimos lo fácil que es usar métodos asincrónicos de EF. Aunque es posible que las ventajas de asincrónico no sean muy evidentes con una aplicación de consola sencilla, estas mismas estrategias se pueden aplicar en situaciones en las que las actividades enlazadas a la red o de larga duración podrían bloquear la aplicación o provocar un gran número de subprocesos para aumentar la superficie de memoria.