.NET

31 mai, 2017

Usando async/await para melhorar a experiência do usuário em uma aplicação WPF

Publicidade

Neste artigo vou recordar como usar os recursos da programação assíncrono usando async/await para melhorar o desempenho de uma aplicação WPF.

Eu já apresentei e discuti os benefícios da programação assíncrona usando async/await neste artigo: C# – Programação Assincrona : async e await. Portanto vou direto para a parte prática onde vamos recordar como aplicar os conceitos relacionados.

No exemplo temos uma aplicação WPF que faz o seguinte:

  • Exibe em um controle ListBox os arquivos de um determinado diretório;
  • Exibe o tempo gasto para a operação de acessar e listar os arquivos em uma Label;
  • Exibe a data e hora atual usando um MessageBox;
  • Todo o código do projeto usa a programação síncrona e nosso objetivo é usar os recursos async/await para melhorar a experiência do usuário.

Vamos então ao projeto…

Recursos usadosVisual Studio 2015 Community

Criando o projeto WPF Application

Abra o VS 2015 Community e crie um novo projeto (File-> New Project) usando a linguagem C# e o template Wpf Applicaiton.

Informe um nome a seu gosto. Eu vou usar o nome Wpf_Async1.

Abra o arquivo MainWindow.xaml e inclua a partir da ToolBox os seguintes controles :

  • 1 Label – lblmsg
  • 2 Buttons – btnLerArquivos, btnExibirDataHora, btnLerArquivos_Click e btnExibirDataHora_Click
  • 1 LisBox – lbArquivos

Disponha os controles conforme o leiaute da figura abaixo:

O código do arquivo XAML gerado pode ser visto a seguir:

 <Window x:Class="Wpf_Async.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:Wpf_Async"
        mc:Ignorable="d"
        WindowStartupLocation="CenterScreen"
        Title="Usando Asycn" Height="439" Width="552">
    <Grid>
        <ListBox x:Name="lbArquivos" HorizontalAlignment="Left" Height="378" Margin="159,10,0,0" VerticalAlignment="Top" Width="358" Background="#FFC7F993"/>
        <Button x:Name="btnLerArquivos" Content="Ler Arquivos" HorizontalAlignment="Left" Margin="21,17,0,0" VerticalAlignment="Top" Width="110" Height="35" Click="btnLerArquivos_Click"/>
        <Button x:Name="btnExibirDataHora" Content="Exibir Data/Hora" HorizontalAlignment="Left" Margin="21,63,0,0" VerticalAlignment="Top" Width="110" Height="35" Click="btnExibirDataHora_Click"/>
        <Label x:Name="lblmsg" Content="" HorizontalAlignment="Left" Margin="21,136,0,0" VerticalAlignment="Top" Height="68" Width="119"/>
    </Grid>
</Window>

Vamos definir agora o código para os eventos Click dos botões no arquivo MainWindow.xaml.cs. Antes de qualquer coisa vamos declarar os seguintes namespaces usados no arquivo:

using System.Collections.Generic;

using System.Linq;

using System.Windows;

using System.IO;

using System.Threading;

using System;

using System.Diagnostics;

1- Evento Click do botão – Ler Arquivos:

        private void btnLerArquivos_Click(object sender, RoutedEventArgs e)
        {
            btnLerArquivos.IsEnabled = false;
            var sw = new Stopwatch();
            sw.Start();
            lbArquivos.ItemsSource = LerArquivos();
            sw.Stop();
            lblmsg.Content = "Tempo Gasto (ms) : \n" + sw.ElapsedMilliseconds.ToString();
            btnLerArquivos.IsEnabled = true;
        }

Neste código desabilitamos o botão e criamos uma instância da classe StopWatch() e iniciamos a contagem do tempo usando o método Start.

Depois chamamos o método LerArquivos() e atribuimos o resultado ao método ItemsSource do controle ListBox para exibir a relação de arquivos retornados.

Paramos a contagem de tempo para a tarefa e exibimos o tempo gasto no controle Label – lblmsg – e habilitamos novamente o botão de comando.

2- Código do método LerArquivos():

        private IEnumerable<string> LerArquivos()
        {
            var arquivos = from arquivo in Directory.GetFiles(@"c:\dados")
                                   select arquivo;
            Thread.Sleep(5000);
            return arquivos;
        }

Este método retorna uma relação de arquivos da pasta c:\dados usando uma consulta LINQ. Para retadar um pouco o código usamos o método Sleep() dando uma pausa de 5 segundos.

3- Código do botão de comando – Exibir Data/Hora:

    private void btnExibirDataHora_Click(object sender, RoutedEventArgs e)
      {
            MessageBox.Show("Hora Data e Hora Atual  :\n " + DateTime.Now.ToShortDateString() + " - "+
                DateTime.Now.ToLongTimeString());
       }

É um exemplo bem simples e ao executar o projeto e clicar no botão – Ler Arquivos – esta operação vai gastar um tempo. Se você clicar no botão – Exibir/Data – vai perceber que nada acontece pois o sistema esta ocupado executando a tarefa para ler e exibir os arquivos.

Pois bem, é isso que queremos evitar.

Como podemos então tornar a nossa aplicação mais responsiva de forma que o usuário possa clicar no botão para ler os arquivos e a operação seja executada de forma assíncrona permitindo que o código do botão Exibir Data/Hora seja executado a qualquer momento visto que a tarefa de leitura de arquivos esta sendo executada de forma assíncrona.

Usando async/await

Para atingir nosso objetivo vamos usar os recursos de async e await. Antes de prosseguir acrescente o seguinte namespace no arquivo MainWindow:

using System.Threading.Tasks;

Definindo uma chamada assíncrona no botão – Ler Arquivos.

Para tornar a chamada do evento Click do botão assíncrona temos que usar palavra async na definição do evento.

Ao usar a palavra async supõe-se que existe algum método neste evento que deva usar a palavra await e que será executado em segundo plano.

O método LerArquivos() é o candidato mais indicado para isso pois assim a tarefa de ler os arquivos será executada em segundo plano. Logo o código fica assim:

private async void btnLerArquivos_Click(object sender, RoutedEventArgs e)
        {
            btnLerArquivos.IsEnabled = false;
            var sw = new Stopwatch();
            sw.Start();
            lbArquivos.ItemsSource = await LerArquivos();
            sw.Stop();
            lblmsg.Content = "Tempo Gasto (ms) : \n" + sw.ElapsedMilliseconds.ToString();
            btnLerArquivos.IsEnabled = true;
        }

Essa abordagem nos obriga a tornar o método LerArquivos() assíncrono. Veja como deve ficar o código desse método:

private Task<IEnumerable<string>> LerArquivos()
        {
            return Task.Run(() =>
            {
                var arquivos = from arquivo in Directory.GetFiles(@"c:\dados")
                               select arquivo;
                Thread.Sleep(5000);
                return arquivos;
            });
        }

O método LerArquivos() agora retorna uma tarefa de coleção de strings ( Task<IEnumerable<string> ), e está preparado para ser executado em segundo plano.

Transformamos assim um método síncrono em um método assíncrono. (É uma boa prática identificar os métodos assíncronos com sufixo Asycn no nome do método).

Dessa forma agora podemos executar o projeto e clicar no botão para exibir os arquivos e a seguir clicar no botão para exibir a data e hora que o código será executado:

Pegue o projeto completo aqui: Wpf_Async.zip