Sabe quando seu aplicativo vai realizar uma tarefa demorada e ele “para de responder” enquanto não termina de processar? Esse é o cenário de grande parte dos aplicativos atualmente.
Com a versão 2.0 do Microsoft .Net Framework foi introduzido um pattern endereçado a esse tipo de cenário chamado Event-based Asynchronous Pattern (EAP), oferecendo as vantagens de uma aplicação multithreaded e "escondendo" a complexidade inerente a esse tipo de design.
Com ele, você pode:
- Executar tarefas demoradas, como downloads e operações de banco de dados, em "background", sem congelar sua aplicação;
- Executar operações simultaneamente e receber notificações quando cada uma for concluída;
- Aguardar que recursos fiquem disponíveis sem congelar sua aplicação;
- Comunicar-se com as operações assíncronas pendentes usando o modelo de eventos;
A efeito de exemplo e aprendizado, o cenário que vamos trabalhar é a leitura de arquivos texto para importação de dados no sistema.
Começamos a implementação do pattern criando as classes de argumento dos eventos que criaremos para nos comunicarmos com o chamador. Essas classes devem herdar do AsyncCompletedEventArgs ou ProgressChangedEventArgs (no caso de informação referente ao progresso da execução).
No nosso caso, a classe padrão (ProgressChangedEventArgs) é suficiente para notificarmos o progresso da leitura e mapeamento dos dados do arquivo. Então, o argumento para a notificação da finalização do processamento ficará:
{
public IEnumerable<Customer> CustomersList { get; private set; }
public MapCompletedEventArgs(IEnumerable<Customer> customersList,
Exception error, bool isCancelled, object userState)
: base(error, isCancelled, userState)
{
this.CustomersList = customersList;
}
}
O próximo passo é criar os handles (delegates) para as operações de avisar o progresso e quando o processamento estiver terminado:
public delegate void MapCompletedEventHandler(MapCompletedEventArgs e);
Agora, criaremos os respectivos eventos para informar o progresso e quando a execução estiver concluída:
public event MapCompletedEventHandler MapCompleted;
Para garantir o uso da thread correta na execução dos eventos precisamos criar callbacks para serem executados via AsyncOperation:
private SendOrPostCallback onCompletedDelegate;
A última variável que precisamos é um dicionário para controlar as múltiplas requisições:
No construtor da classe inicializamos os callbacks:
{
onProgressReportDelegate = new SendOrPostCallback(ReportMapProgress);
onCompletedDelegate = new SendOrPostCallback(ContentFileMappingCompleted);
}
private void ContentFileMappingCompleted(object operationState)
{
OnMapCompleted(operationState as MapCompletedEventArgs);
}
private void OnMapCompleted(MapCompletedEventArgs e)
{
if (MapCompleted != null)
{
MapCompleted(e);
}
}
private void ReportMapProgress(object state)
{
OnMapProgressChanged(state as ProgressChangedEventArgs);
}
private void OnMapProgressChanged(ProgressChangedEventArgs e)
{
if (MapProgressChanged != null)
{
MapProgressChanged(e);
}
}
E finalmente chegamos aos métodos de mapeamento assíncrono do arquivo, o qual, criamos uma operação assíncrona através da classe AsyncOperationManager:
{
var asyncOp = AsyncOperationManager.CreateOperation(filePath);
lock (userStateToLifetime.SyncRoot)
{
if (!userStateToLifetime.Contains(filePath))
{
userStateToLifetime[filePath] = asyncOp;
}
}
var action = new Action<string, AsyncOperation>(MapFileWorker);
return action.BeginInvoke(filePath, asyncOp, null, null);
}
Disponiblizaremos um método para ser utilizado caso o usuário queira cancelar a operação a qualquer momento:
{
var asyncOp = userStateToLifetime[filePath] as AsyncOperation;
if (asyncOp != null)
{
lock (userStateToLifetime.SyncRoot)
{
userStateToLifetime.Remove(filePath);
}
}
}
No método que realiza o processamento da requisição precisamos garantir que o evento referente a finalização do processamento seja executado em caso de sucesso, cancelamento ou falha. E para executarmos o evento utilizamos o método PostOperationComplete da operação assíncrona que criamos:
{
Exception error = null;
IEnumerable<Customer>customersList = null;
if (!TaskCanceled(asyncOp.UserSuppliedState))
{
try
{
customersList = MapFileLogic(filePath, asyncOp);
}
catch (Exception ex)
{
error = ex;
}
}
if (!TaskCanceled(asyncOp.UserSuppliedState))
{
lock (userStateToLifetime.SyncRoot)
{
userStateToLifetime.Remove(asyncOp.UserSuppliedState);
}
}
var arg = new MapCompletedEventArgs(customersList, error,
TaskCanceled(asyncOp.UserSuppliedState), asyncOp.UserSuppliedState);
asyncOp.PostOperationCompleted(onCompletedDelegate, arg);
}
Para o evento de acompanhamento do progresso usamos o método Post da operação assíncrona:
asyncOp.Post(this.onProgressReportDelegate, arg);
Ao final, a classe que utilizará o método assíncrono ficará assim:
mapper.MapCompleted += new CustomerMapper.MapCompletedEventHandler(mapper_MapCompleted);
mapper.MapProgressChanged += new CustomerMapper.MapProgressChangedEventHandler(mapper_MapProgressChanged);
mapper.MapAsync(Environment.CurrentDirectory + @"\CustomerTextFile1.txt");
E talvez você esteja se pergutando “Legal, e por que eu deveria me importar com isso?”.
Essa é uma tendência de programação que ganhou força com o Silverlight e parece ser o padrão para se programar no Windows Phone 7.
Será que esse pattern se tornará padrão para programação de aplicações no futuro?
Você já usou esse padrão?
Até a próxima!
DOWNLOAD EXEMPLO:
EAP.TEXTFILEMAPPER.RAR
Artigos Recomendados:
>> Software para Pessoas do Séc XXI>> Prepare-se para o C# 5 – Parte 2
>> Prepare-se para o C# 5 – Parte 1
>> Reflection de Alta de Performance
>> Extension Methods = Manutenibilidade