Partilhar via


Consulta e armazenamento assíncronos

Observação

Apenas a partir do EF6 - As funcionalidades, APIs, etc. discutidas nesta página foram introduzidas no Entity Framework 6. Se estiver a usar uma versão anterior, parte ou toda a informação não se aplica.

O EF6 introduziu suporte para consulta e gravação assíncronas usando as palavras-chave async e await que foram introduzidas no .NET 4.5. Embora nem todas as aplicações possam beneficiar da assíncronia, esta pode ser usada para melhorar a resposta do cliente e a escalabilidade do servidor ao lidar com tarefas de longa duração, com rede ou I/O limitadas.

Quando realmente usar assíncrono

O objetivo deste guia é introduzir os conceitos assíncronos de uma forma que facilite a observação da diferença entre execução de programas assíncronas e síncronas. Este guia não pretende ilustrar nenhum dos cenários-chave em que a programação assíncrona oferece benefícios.

A programação assíncrona foca-se principalmente em libertar o thread gerido atual (thread a correr código .NET) para realizar outros trabalhos enquanto espera por uma operação que não exija tempo de computação de um thread gerido. Por exemplo, enquanto o motor de base de dados processa uma consulta, não há nada a fazer com código .NET.

Em aplicações cliente (WinForms, WPF, etc.) o thread atual pode ser usado para manter a interface responsiva enquanto a operação assíncrona é realizada. Em aplicações servidor (ASP.NET etc.), a thread pode ser usada para processar outros pedidos recebidos – isto pode reduzir o uso de memória e/ou aumentar o rendimento do servidor.

Na maioria das aplicações, usar assíncrono não terá benefícios notórios e pode até ser prejudicial. Usa testes, perfilagem e bom senso para medir o impacto do assíncrono no teu cenário particular antes de te comprometeres com ele.

Aqui estão mais alguns recursos para aprender sobre async.

Criar o modelo

Vamos usar o fluxo de trabalho Code First para criar o nosso modelo e gerar a base de dados, no entanto, a funcionalidade assíncrona funcionará com todos os modelos EF, incluindo os criados com o EF Designer.

  • Crie uma aplicação de consola e chame-lhe AsyncDemo
  • Adicionar o pacote NuGet do EntityFramework
    • No Explorador de Soluções, clique com o botão direito no projeto AsyncDemo
    • Selecionar Gerir Pacotes do NuGet...
    • No diálogo Gestão de Pacotes NuGet, selecione o separador Online e escolha o pacote EntityFramework
    • Clique em Instalar
  • Adicionar uma classe Model.cs com a seguinte implementação
    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; }
        }
    }

 

Criar um programa síncrono

Agora que temos um modelo EF, vamos escrever algum código que o use para realizar algum acesso a dados.

  • Substitua o conteúdo de Program.cs pelo seguinte código
    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 chama o PerformDatabaseOperations método que guarda um novo Blog na base de dados e depois recupera todos os Blogs da base de dados e imprime-os na Consola. Depois disso, o programa escreve uma citação do dia para a Consola.

Como o código é síncrono, podemos observar o seguinte fluxo de execução quando executamos o programa:

  1. SaveChanges começa a empurrar o novo Blog para a base de dados
  2. SaveChanges Completa
  3. A consulta para todos os blogs é enviada para a base de dados
  4. Os retornos e resultados das consultas são escritos para a Consola
  5. Citação do dia é escrita na Consola

Saída de Sincronização  

 

Tornando-o assíncrono

Agora que temos o nosso programa a funcionar, podemos começar a usar as novas palavras-chave async e await. Fizemos as seguintes alterações à Program.cs

  1. Linha 2: A instrução using para o System.Data.Entity namespace dá-nos acesso aos métodos de extensão assíncrona do EF.
  2. Linha 4: A instrução using para o namespace System.Threading.Tasks permite-nos usar o tipo Task.
  3. Linhas 12 e 18: Estamos a capturar uma tarefa que monitoriza o progresso de PerformSomeDatabaseOperations (linha 12) e depois a bloquear a execução do programa para que esta tarefa seja concluída assim que todo o trabalho do programa estiver concluído (linha 18).
  4. Linha 25: Atualizámos PerformSomeDatabaseOperations para ser marcado como async e devolver um Task.
  5. Linha 35: Estamos agora a chamar a versão assíncrona de SaveChanges e a aguardar a sua conclusão.
  6. Linha 42: Estamos agora a chamar a versão Assíncrona de ToList e a aguardar o resultado.

Para uma lista abrangente dos métodos de extensão disponíveis no System.Data.Entity namespace, consulte a QueryableExtensions classe. Também vais precisar de acrescentar using System.Data.Entity às tuas instruções de uso.

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

Agora que o código é assíncrono, podemos observar um fluxo de execução diferente quando executamos o programa:

  1. SaveChanges começa a empurrar o novo Blog para a base de dados
    Uma vez enviado o comando para a base de dados, não é necessário mais tempo de cálculo na thread gerida atual. O PerformDatabaseOperations método retorna (mesmo que ainda não tenha terminado a execução) e o fluxo do programa no método Main continua.
  2. A citação do dia é exibida na Consola
    Como já não há trabalho a fazer no método Main, o thread gerido fica bloqueado na Wait chamada até que a operação da base de dados seja concluída. Quando estiver concluído, o restante da nossa PerformDatabaseOperations será executado.
  3. SaveChanges Completa
  4. A consulta para todos os blogs é enviada para a base de dados
    Mais uma vez, o thread gerido fica livre para fazer outros trabalhos enquanto a consulta é processada na base de dados. Como todas as outras execuções já foram concluídas, a thread simplesmente para na chamada do método Wait.
  5. Os retornos e resultados das consultas são escritos para a Consola

Saída Assíncrona  

 

A conclusão

Agora vimos como é fácil utilizar os métodos assíncronos do EF. Embora as vantagens do assíncrono possam não ser muito evidentes numa simples aplicação de consola, estas mesmas estratégias podem ser aplicadas em situações onde atividades de longa duração ou ligadas à rede poderiam bloquear a aplicação ou levar um grande número de threads a aumentar o consumo de memória.