Partilhar via


Consulta assíncrona e salvar

Observação

EF6 em diante apenas: os recursos, as APIs etc. discutidos nessa página foram introduzidos no Entity Framework 6. Se você estiver usando uma versão anterior, algumas ou todas as informações não se aplicarão.

O EF6 introduziu suporte para consulta assíncrona e salvamento usando as palavras-chave async e await que foram introduzidas no .NET 4.5. Embora nem todos os aplicativos possam se beneficiar da assincronia, ela pode ser usada para melhorar a capacidade de resposta do cliente e a escalabilidade do servidor ao lidar com tarefas de longa execução, de rede ou ligadas a E/S.

Quando realmente usar o assíncrono

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

A programação assíncrona se concentra principalmente em liberar o thread gerenciado atual (thread executando código .NET) para fazer outro trabalho enquanto aguarda uma operação que não requer nenhum tempo de computação de um thread gerenciado. Por exemplo, enquanto o mecanismo de banco de dados está processando uma consulta, não há nada a ser feito pelo código .NET.

Em aplicativos cliente (WinForms, WPF, etc.) o thread atual pode ser usado para manter a interface do usuário responsiva enquanto a operação assíncrona é executada. Em aplicativos de servidor (ASP.NET etc.) o thread pode ser usado para processar outras solicitações de entrada - isso pode reduzir o uso de memória e/ou aumentar a taxa de transferência do servidor.

Na maioria dos aplicativos usando assíncrono não terá benefícios perceptíveis e até mesmo pode ser prejudicial. Use testes, criação de perfil e bom senso para medir o impacto do assíncrono em seu cenário específico antes de se comprometer com ele.

Aqui estão mais alguns recursos para aprender sobre assíncrono:

Criar o modelo

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

  • Crie um aplicativo de console e chame-o de AsyncDemo
  • Adicionar o pacote NuGet do EntityFramework
    • No Gerenciador de Soluções, clique com o botão direito do mouse no projeto AsyncDemo
    • Selecione Gerenciar pacotes NuGet...
    • Na caixa de diálogo Gerenciar Pacotes NuGet, selecione a guia Online e escolha o pacote EntityFramework
    • Clique em Instalar
  • Adicione 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 executar algum acesso a dados.

  • Substitua o conteúdo de Programa.cs com o 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);
                    }
                }
            }
        }
    }

Esse código chama o método PerformDatabaseOperations que salva um novo Blog no banco de dados e, em seguida, recupera todos os Blogs do banco de dados e os imprime no Console. Depois disso, o escreve grava uma citação do dia para o Console.

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 o banco de dados
  2. SaveChanges completa
  3. A consulta de todos os Blogs é enviada para o banco de dados
  4. Os retornos e resultados da consulta são gravados no Console
  5. A citação do dia está escrita em Console

Sync Output 

 

Tornando-o assíncrono

Agora que temos nosso programa em funcionamento, podemos começar a fazer uso das novas palavras-chave assíncronas e aguardar. Fizemos as seguintes alterações no Programa.cs

  1. Linha 2: a instrução using para o namespace System.Data.Entity nos dá acesso aos métodos de extensão assíncrona do EF.
  2. Linha 4: a instrução using para o namespace System.Threading.Tasks nos permite usar o tipo Task.
  3. Linhas 12 e 18: estamos capturando como tarefa que monitora o progresso de PerformSomeDatabaseOperations (linha 12) e, em seguida, bloqueando a execução do programa para que essa tarefa seja concluída assim que todo o trabalho para o programa for concluído (linha 18).
  4. Linha 25: atualizamos PerformSomeDatabaseOperations para ser marcado como async e retornar um Task.
  5. Linha 35: agora estamos chamando a versão assíncrona do SaveChanges e aguardando sua conclusão.
  6. Linha 42: agora estamos chamando a versão assíncrona do ToList e aguardando o resultado.

Para obter uma lista abrangente de métodos de extensão disponíveis no namespace System.Data.Entity, consulte a classe QueryableExtensions. Você também precisará adicionar using System.Data.Entity às suas 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 o banco de dados
    Depois que o comando é enviado para o banco de dados, não é necessário mais tempo de computação no thread gerenciado atual. O método PerformDatabaseOperations retorna (mesmo que não tenha terminado a execução) e o fluxo do programa no método Main continua.
  2. A citação do dia está escrita no Console
    Como não há mais trabalho a ser feito no método Main, o thread gerenciado é bloqueado na chamada Wait até que a operação do banco de dados seja concluída. Assim que terminar, o restante do nosso PerformDatabaseOperations será executado.
  3. SaveChanges completa
  4. A consulta de todos os Blogs é enviada para o banco de dados
    Novamente, o thread gerenciado está livre para fazer outro trabalho enquanto a consulta é processada no banco de dados. Como todas as outras execuções foram concluídas, o thread será interrompido na chamada de espera.
  5. Os retornos e resultados da consulta são gravados no Console

Async Output 

 

Resumo

Agora vimos como é fácil fazer uso dos métodos assíncronos do EF. Embora as vantagens da assíncrona possam não ser muito aparentes com um aplicativo de console simples, essas mesmas estratégias podem ser aplicadas em situações em que atividades de execução longa ou associadas à rede podem bloquear o aplicativo ou fazer com que um grande número de threads aumente o volume de memória.