Exibir o progresso da operação;
Obter o resultado na thread que iniciou a execução assíncrona;
Cancelar a operação;
Monitorar o estado da operação.
Este componente é o BackgroundWorker (System.ComponentModel.BackgroundWorkder).
Para aplicações WindowsForms adicionar este componente é tão simles quanto realizar um drag and drop do mesmo na parte Components da Toolbox do Visual Studio, para as demais aplicações
é necessário declarar e instanciar um objeto BackgroundWorker, o que é também um trabalho trivial.
Um vez que temos um BackgroundWorker em mãos vamos ao que nos interessa. Primeiramente vamos supor que o código que desejamos executar de forma assíncrona é o representado pelo método BaixeArquivos, abaixo:
private void BaixeArquivos(string[] urls)
{
foreach (string url in urls)
{
BaixeArquivos(url, DiretorioDeDestino);
}
}
Obs.: Considere também que BaixeArquivo é um método que realmente realiza o download de um dado arquivo, com base na url do mesmo, e que DiretorioDeDestino é uma constante da aplicação.
Para que este código seja executado via BackgroundWorker é necessário invoca-lo de dentro do evento DoWork:
BackgroundWorker worker = new BackgroundWorker(); // variável de instância
void AlgumMetodo()
{
//codigo qualquer
worker.DoWork += (Worker_DoWork);
worker.RunWorkerAsync(
new string[]
{
"http://solucoesageis.com.br/Portals/0/logoNFe.JPG",
"http://www.google.com.br/intl/pt-BR_br/images/logo.gif",
"http://www.msdnbrasil.com.br/anp/img/tit_ranking_msdn.gif"
});
//codigo qualquer
}
void Worker_DoWork(object sender, DoWorkEventArgs e)
{
BaixeArquivos(e.Argument as string[]);
}
O código acima já ilustra duas operações do componente em questão: a criação do método que irá executar de forma assíncrona(assinar o evento DoWork) e o disparo da operação (invocar o método RunWorkerAsync). Note que os argumentos necessários para a execução da operação são passados atravéz do argumento do método RunWorkerAsync, que é do tipo object, e depois deve ser convertido para o tipo apropriado dentro do código que executa a operação.
Neste ponto já sabemos criar um BackgroundWorker, assinar o seu evento principal fornecer os argumentos necessários, iniciar a operação, obter os argumentos(via DoWorkEventArgs.Argument) e realizar o trabalho necessário. Para situções mais simples isso já é o suficiente, porém se o caso for esse aconselho o uso de delegates, pois se trata do caso mais simples de execução assíncrona.
Então o que mais pode ser necesário ??
Relatar Progresso: para relatar progresso é necessário assinar o evento ProgressChanged e invocar o método ReportProgress. Veja a aplicação desta estratégia em nosso estudo de caso:
void AlgumMetodo()
{
//codigo qualquer
worker.DoWork += (Worker_DoWork);
worker.ProgressChanged += (Worker_ProgressChanged);
worker.RunWorkerAsync(
new string[]
{
"http://solucoesageis.com.br/Portals/0/logoNFe.JPG",
"http://www.google.com.br/intl/pt-BR_br/images/logo.gif",
"http://www.msdnbrasil.com.br/anp/img/tit_ranking_msdn.gif"
});
//codigo qualquer
}
void Worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
Console.Write("Operação " + e.ProgressPercentage + "% concluída.");
}
private void BaixeArquivos(string[] urls)
{
int cont = 0;
foreach (string url in urls)
{
BaixeArquivos(url, DiretorioDeDestino);
worker.ReportProgress((++cont)/urls.Length);
}
}
Veja que o método que relata o progresso é o que assina o evento ProgressChanged e que o método ReportProgess deve ser invocado de dentro do código que é executado assíncronamente, pois este deve saber em qual porcentagem de conclusão ele se encontra.
Outro detalhe é a propriedade WorkerReportsProgress que indica se o componente irá invocar o evento ProgressChanged quando o método ReportProgress for chamado, o valor dessa propriedade é true por default.
Pronto agora já sabemos como relatar progresso, o controle ProgressBar poderia ser usado no lugar da escrita no Console para aplicações WindowsForms. Então vamos à próxima necessidade:
Cancelar a operação: para cancelar uma operação que foi iniciada por um BackgroundWorker é necessário invocar o método CancelAsync que irá disparar o evento RunWorkerCompleted. Quando este método é invocado o propriedade CancellationPending é configurada com true, e então o valor dessa variável deve ser verificado dentro do método que assina o evento DoWork para que neste a lógica de cancelamento seja executada. Assim como no relatório de progresso você tem a opção de desabilitar o cancelamento da operação atravéz da porpriedade WorkerSuportsCancellation, que é true por default. Veja a ilustração de um cancelameno abaixo:
void AlgumMetodo()
{
//codigo qualquer
worker.DoWork += (Worker_DoWork);
worker.ProgressChanged += (Worker_ProgressChanged);
worker.RunWorkerCompleted += (Worker_RunWorkerCompleted);
worker.RunWorkerAsync(
new string[]
{
"http://solucoesageis.com.br/Portals/0/logoNFe.JPG",
"http://www.google.com.br/intl/pt-BR_br/images/logo.gif",
"http://www.msdnbrasil.com.br/anp/img/tit_ranking_msdn.gif"
});
//codigo qualquer
}
private void VerificaSeEhNecessarioCancelar()
{
// Verificou-se que é necessário cancelar
worker.CancelAsync();
}
void Worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Cancelled)
}
private void BaixeArquivos(string[] urls)
{
int cont = 0;
foreach (string url in urls)
{
if (worker.CancellationPending)
{
// Possível lógica de cancelamento deve estar aqui
e.Cancel = true;
return;
}
BaixeArquivos(url, DiretorioDeDestino);
worker.ReportProgress((++cont)/urls.Length);
}
Observe que a lógica de cancelamento deve ser inserida no código que está sendo executado de forma assíncrona. Além disso note que evento para este caso é o evento de conclusão da operação, isto é, ele será invocado não só apenas quando houver cancelamento da operação, e sim sempre que esta for conlcuída, independentemente do resultado. Com base nisto vamos para o próximo requisito:
Obter o resultado da operação: como você já sabe assinar o evento que será disparado no momento de conclusão da operação resta apenas um detalhe: indicar o resultado, isto é feito atravéz da propriedade DoWorkEventArgs.Result. Como a propriedade Result só pode ser obtida de dentro do método que assina o evento DoWork teremos que migrar o código do método BaixeArquivos para dentro do método Workder_DoWork (claro que existem outras formas de se fazer isto, mas vamos simplificar). Vamos ao código:
void Worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Cancelled)
Console.WriteLine("Operação cancelada");
else
}
void Worker_DoWork(object sender, DoWorkEventArgs e)
{
string urls = e.Argument as string[];
int cont = 0;
foreach (string url in urls)
{
if (worker.CancellationPending)
{
// Possível lógica de cancelamento deve estar aqui
e.Cancel = true;
return;
}
BaixeArquivos(url, DiretorioDeDestino);
worker.ReportProgress((++cont)/urls.Length);
}
e.Result = "Downalods Realizados";
}
A propriedade Result é do tipo Object, o que permite que qualquer tipo de resultado seja retornado atravéz dela.
A última manipulação que será explicada é a consulta do status de uma operação assíncrona.
Requisitando o status de uma operação: Em algum momento de seu código pode ser necessário saber se a operação está acontecendo, ou se ela já está concluída (pois nem sempre o resultado da mesma será retornado), resumindo, pode ser necessário saber se o componente BackgroundWorker está trabalhando ou se ele está livre. Exemplo: para poder iniciar uma nova operação, no caso desta operação não poder ser executada mais de uma vez ao mesmo tempo. Para saber isso é preciso consultar a propriedade IsBusy do BackgroundWorker. Código:
private void TenteLancarOperacao()
{
if (!worker.IsBusy)
{
worker.RunWorkerAsync(
new string[]
{
"http://solucoesageis.com.br/Portals/0/logoNFe.JPG",
"http://www.google.com.br/intl/pt-BR_br/images/logo.gif",
"http://www.msdnbrasil.com.br/anp/img/tit_ranking_msdn.gif"
});
}
}
Espero que neste ponto você considere um novo componente para sua caixa de ferramentas de programação. Existem mil formas de se fazer a mesma coisa, e tudo indica que devemos dar preferência para o caminho mais curto e o BackgroundWorker é um caminho curto para operações assíncronas onde é necessário um acompanhamento minucioso do processo, pois neste a parte burocrática já está pronta, basta inserir o código certo no local certo.
Até o próximo post!
2 comentários:
Obrigado pela explicação. ajudou me mt
Parabéns amigo.
Artigo bem escrito e lucidativo sobre o BackGroundWorker. É raro de encontrar em lingua portuguesa escritores que desenvolvam suas idéias e não simplesmente copiam e traduzem idéias de outros.
Postar um comentário