Elemar DEV

Tecnologia e desenvolvimento

Async (advanced) – Returning Values, Cancellation, Awaiter pattern

Olá pessoal. Tudo certo?!

No último post, mostrei como programação assíncrona ficou mais fácil (e natural) a partir do Async CTP (que antecipa muitas funcionalidades interessantes do C# 5.0).

Nesse post, apresento mais uma série de possibilidades com essa tecnologia.

Retornando valores

No post anterior, mostrei como implementar uma consulta a Web, retornando a coleção de Urls presentes em um links de uma página. Relembrando:

public async void PrintLinksFromMSDNAsync()
{
    var response = await new WebClient().
		DownloadStringTaskAsync("http://msdn.microsoft.com/pt-br/");
    var regex = "href\\s*=\\s*(?:\"(?[^\"]*)\"|(?\\S+))";
    var matches = Regex.Matches(response, regex);

    foreach (Match match in matches)
        Console.WriteLine(match.Groups[1]);
}

Mas, o que fazer se desejássemos retornar essa coleção de links no lugar de imprimir na tela?

O modificador async só é válido para métodos cujo retorno é void, Task ou Task<T>. Complicou? Nada! Observe:

public async Task<IEnumerable<string>> GetLinksFromMsdnAsync()
{
    var response = await new WebClient().
		DownloadStringTaskAsync("http://msdn.microsoft.com/pt-br/");
    var regex = "href\\s*=\\s*(?:\"(?<1>[^\"]*)\"|(?<1>\\S+))";
    return Regex.Matches(response, regex)
            .OfType<Match>()
            .Select(m => m.Groups[1].Value);
}

Usando LINQ to objects, converti os matches em uma IEnumerable<string>. Repare que não precisei me preocupar em criar uma Task efetivamente. Esse trabalho é feito nos bastidores pelo compilador.

Para consumir, trabalhamos novamente com async/await. Observe:

public async void PrintLinksFromMSDNAsync()
{
    var links = await GetLinksFromMsdnAsync();
    foreach (var link in links)
        Console.WriteLine(link);
}

Cancellation

Operações assíncronas podem consumir bastante tempo. Por isso, é importante permitir, de alguma forma, que o usuário consiga cancelar a operação. Graças a TPL, isso é muito simples.

Comecemos por um layout muito simples em WPF (xaml):

<Window x:Class="AsyncWpf.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"></RowDefinition>
            <RowDefinition Height="auto"></RowDefinition>
        </Grid.RowDefinitions>
        <ListBox Name="output"></ListBox>
        <StackPanel Grid.Row="1" Orientation="Horizontal">
            <Button  Name="StartButton" Click="StartButtonClick">Start</Button>
            <Button  Name="StopButton" IsEnabled="False" Click="StopButtonClick">Stop</Button>
        </StackPanel>
    </Grid>
</Window>

Observe que não utilizei nenhuma boa prática MVVM. Apenas o básico para nossa aplicação funcionar. Agora, o código com suporte a Cancellation. Observe:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    private void StartButtonClick(object sender, RoutedEventArgs e)
    {
        this.Update();
    }

    private void StopButtonClick(object sender, RoutedEventArgs e)
    {
        cts.Cancel();
    }

    CancellationTokenSource cts;

    async void Update()
    {
        try
        {
            this.StartButton.IsEnabled = false;
            this.StopButton.IsEnabled = true;
            cts = new CancellationTokenSource();
            this.output.ItemsSource = null;
            this.output.ItemsSource = await GetLinksFromMsdnAsync();
        }
        catch (TaskCanceledException)
        {
            MessageBox.Show("Cancelled!");
        }
        finally
        {
            this.StartButton.IsEnabled = true;
            this.StopButton.IsEnabled = false;
        }
    }

    async Task<IEnumerable<string>> GetLinksFromMsdnAsync()
    {
        var response = await new WebClient()
            .DownloadStringTaskAsync(
                new Uri("http://msdn.microsoft.com/pt-br/"),
                cts.Token
            );

        var regex = "href\\s*=\\s*(?:\"(?<1>[^\"]*)\"|(?<1>\\S+))";

        return Regex.Matches(response, regex)
                .OfType<Match>()
                .Select(m => m.Groups[1].Value);
    }
}

Pegou a idéia?! É o mesmo método para cancellation que conhecemos com a TPL. Certo?!

Awaiter pattern

Uma das características mais bacanas do Async é que ele é baseado em patterns de implementação. Ou seja, no lugar de esperar a implementação de uma interface ou a especialização de uma classe abstrata, basta que o objeto “após” o modificador await defina o método GetAwaiter (mesmo que com um extension method).

O método GetAwaiter precisa devolver um objeto que possua os métodos void OnCompleted (com um único argumento, do tipo Action, que serve como continuation) e TResult GetResult (onde TResult pode ser void). Além disso, ele deve prover uma propriedade bool chamada IsCompleted.

Comecemos com um exemplo simples:

using System;
using System.Threading.Tasks;
using System.Runtime.CompilerServices;

namespace AsyncDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            new Program().Run();
            Console.WriteLine("Press any key to exit...");
            Console.ReadLine();
        }

        public async void Run()
        {
            await 5000;
            Console.WriteLine("Five seconds after");
        }
    }

    public static class AwaiterExtensions
    {
        public static TaskAwaiter GetAwaiter(this int milliseconds)
        {
            return TaskEx.Delay(new TimeSpan(0, 0, 0, 0, milliseconds)).GetAwaiter();
        }
    }
}

Nesse exemplo, criei um extension method para prover o TaskAwaiter. Bonito, não?! (Não está habituado com extension methods? Dê uma olhada em um post que escrevi sobre o tema).

Que tal esperar o encerramento de um processo?!

namespace AsyncDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            new Program().Run();
            Console.WriteLine("Press any key to exit...");
            Console.ReadLine();
        }

        public async void Run()
        {
            await Process.Start("Notepad.exe");
            Console.WriteLine("Notepad was finished");
        }
    }

    public static class AwaiterExtensions
    {
        public static TaskAwaiter<int> GetAwaiter(this Process that)
        {
            var tcs = new TaskCompletionSource<int>();
            that.EnableRaisingEvents = true;
            that.Exited += (s, e) => tcs.TrySetResult(that.ExitCode);
            if (that.HasExited) tcs.TrySetResult(that.ExitCode);
            return tcs.Task.GetAwaiter();
        }
    }
}

Dá para ver que a coisa pode ir longe… não dá?!

Era isso!

3 comentários em “Async (advanced) – Returning Values, Cancellation, Awaiter pattern

  1. Rafael Zaccanini
    03/01/2012

    Gostei … :)

  2. Que maravilhas

  3. alexsandro_xpt
    01/05/2012

    Super demais! estou procurando agora IProgress.
    Elemar, seus posts são incríveis!!!

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s

Informação

Publicado às 03/01/2012 por em Post e marcado , .

Estatísticas

  • 626,540 hits
%d blogueiros gostam disto: