quarta-feira, 20 de abril de 2011

Event-based Asynchronous Pattern (EAP). O futuro padrão para as interfaces das aplicações?

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 class MapCompletedEventArgs : AsyncCompletedEventArgs
{
  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 MapProgressChangedEventHandler(ProgressChangedEventArgs e);
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 MapProgressChangedEventHandler MapProgressChanged;
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 onProgressReportDelegate;
private SendOrPostCallback onCompletedDelegate;

A última variável que precisamos é um dicionário para controlar as múltiplas requisições:

private HybridDictionary userStateToLifetime = new HybridDictionary();

No construtor da classe inicializamos os callbacks:

public CustomerMapper()
{
  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:

public IAsyncResult MapAsync(string filePath)
{
  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:

public void CancelReadAsync(string filePath)
{
  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:

private void MapFileWorker(string filePath, AsyncOperation asyncOp)
{
  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:

var arg = new ProgressChangedEventArgs(progressPercentage, asyncOp.UserSuppliedState);
asyncOp.Post(this.onProgressReportDelegate, arg);

Ao final, a classe que utilizará o método assíncrono ficará assim:

var mapper = new CustomerMapper();
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

terça-feira, 12 de abril de 2011

Software para Pessoas do Séc XXI

Antigamente quando você dizia “Chefe, chefe, estou com uma grande idéia”, ele te dizia, “Calma. Respira fundo que passa.”

Hoje, as necessidades mudaram. As pessoas estão se despedindo dos seus chefes e apenas em 2011 foram 163.679 formalizações de empreendedores individuais segundo o Sebrae.

Antes, ao comprar uma televisão você virava um consumidor, agora, ao comprar qualquer eletrônico você se torna um produtor. Já percebeu quanto material é produzido por pessoas como você em eventos, festas, desastres, etc? Somos mais rápido que a imprensa!

O tempo virou o bem social coletivo. Relevância, a palavra chave nesse mar de informação. E co-criação a saída de uma vida moderna sem significado. Você conhece o Camiseteria? Ou o Electrolux Design Lab? Talvez o Fiat Mio? Provavelmente o Nespresso? Mas com certeza o Wikipedia!

As pessoas mudaram. Você mudou! Só que as empresas ainda criam software como na época do “Departamento de Processamento de Dados”? rs

"Empresas que compreendem a importância de aproveitar o poder dos comportamentos colectivos para impulsionar mudanças positivas nos negócios serão bem-sucedidas." – Gartner

Em projetos de softwares modernos além da arquitetura do software, da estrutura de dados, da distribuição e da sergurança precisamos considerar as capacidades e limitações dos usuários, de forma a tornar o trabalho deles mais eficaz, eficiente e agradável.

Para isso, primeiramente sua metologia deve promover o envolvimento do usuário no projeto.

Como o usuário é a pessoa que mais conhece sobre o contexto do seu trabalho, sem desconsiderar o risco de perda de tempo e recursos em função da variabilidade e subjetividade que caracterizam as atividades com usuários, devemos desenvolver técnicas para planejar, organizar e executar o envolvimento adequado, que pode ser:

  • Informativo: o usuário é visto como fonte de informação que são extraídas através de técnicas de entrevistas, questionários ou observação;
  • Consultivo: o projetista, valendo-se ou não das informações coletadas, elabora soluções de projeto e pede que o usuário as verifique e emita uma opinião;
  • Participativo: quando a corporação transfere ao usuário o poder sobre as decisões de projeto e requer alto engajamento (que deve vir desde a alta gerência);

Durante a especificação do sistema e o desenvolvimento do protótipo você deve trabalhar os 5 fatores da experiência do usuário:

  • Utilidade: percepção do usuário quanto a funcionalidade lhe agregar algum valor dentro do seu contexto;
  • Usabilidade: diz respeito à eficácia, eficiência e satisfação do usuário na realização de seus objetivos com o sistema;
  • Disponibilidade: refere-se aos elementos da interface que fornecem feedback sobre o estado do sistema, mecanismos que evitem perda de informação, etc;
  • Estética: refere-se ao apelo visual da aplicação, à sua atratividade para o usuário;
  • Processo off-line: complementa a experiência do usuário, como a confiança no nome da empresa, a segurança dos dados, suporte, treinamento, campanha de marketing, entre outros;

O IPhone é um exemplo de produto criado com esse tipo de abordagem.

O que achou? Deixe seus comentários.

Até o próximo!




Artigos Recomendados:

>>   Redes Sociais no Ambiente Corporativo
>>   Pensando em ERGONOMIA e USABILIDADE
>>   Design centrado no usuário