WPF  -  Usando async/await para melhorar a experiência do usuário


 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 - Macoratti.net

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:

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 Usados

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 :

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 esta 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

(Disse Jesus) - 'Aquele que tem os meus mandamentos e os guarda esse é o que me ama; e aquele que me ama será amado de meu Pai, e eu o amarei, e me manifestarei a ele.'
João 14:21
 

Veja os Destaques e novidades do SUPER DVD Visual Basic (sempre atualizado) : clique e confira !

Quer migrar para o VB .NET ?

Quer aprender C# ??

Quer aprender os conceitos da Programação Orientada a objetos ?

Quer aprender o gerar relatórios com o ReportViewer no VS 2013 ?

Quer aprender a criar aplicações Web Dinâmicas usando a ASP .NET MVC 5 ?

 

  Gostou ?   Compartilhe no Facebook   Compartilhe no Twitter

 

Referências:


José Carlos Macoratti