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.
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);
}
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?!
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!
Gostei …
Que maravilhas
Super demais! estou procurando agora IProgress.
Elemar, seus posts são incríveis!!!