Vamos ao nosso cenário básico: imagine uma classe responsável por disparar alguns workers em background de acordo com a tarefa que cada um deve executar. Esses procedimentos por si geram notificações através de eventos que podem ser tratados por outras classes da sua solução. Nada demais, o código para isso (bem simplificado) poderia ser assim:
Por simplicidade deste exemplo eu faço um Join() da thread criada, esperando o fim da execução, para deixar esse exemplo bem controlado, mas na aplicação de verdade isso é diferente.
public struct InfoTarefa
{
public string Info;
public DateTime horaExecução;
}
public class NegocioTeste
{
public void ExecutaTarefa(object informação)
{
InfoTarefa tarefa;
tarefa.Info = (string)informação;
tarefa.horaExecução = DateTime.Now;
if (TarefaCompleta != null)
TarefaCompleta(tarefa);
}
public delegate void NotificarTarefaCompleta(InfoTarefa tarefa);
public event NotificarTarefaCompleta TarefaCompleta;
}
public class ThreadEmBackground
{
public Listtarefas;
private NegocioTeste negocio;
public ThreadEmBackground(NegocioTeste n)
{
negocio = n;
}
public void ProcessaTarefas()
{
foreach (string tarefa in tarefas)
{
Thread t = new Thread(negocio.ExecutaTarefa);
t.Start(tarefa);
t.Join();
}
}
}
Para testar o código acima basta fazermos o setup correto das pré-condições, colocando as verificações do resultado na rotina de tratamento do evento TarefaCompleta.
Mas se a verificação falhasse no tratamento do nosso evento, que acontece em outra thread, olhe na figura abaixo o que veríamos:
[TestMethod]
public void TesteBásico()
{
//SETUP
Listtarefas = new List ();
tarefas.Add("Tarefa 01");
NegocioTeste negocio = new NegocioTeste();
ThreadEmBackground gerenteThreads = new ThreadEmBackground(negocio);
gerenteThreads.tarefas = tarefas;
negocio.TarefaCompleta += RecebeInfoTarefa;
Assert.AreEqual(1, gerenteThreads.tarefas.Count);
// Execução do curso básico
gerenteThreads.ProcessaTarefas();
}
public void RecebeInfoTarefa(TestesMultiThread.InfoTarefa tarefa)
{
Assert.AreEqual("Tarefa 01", tarefa.Info);
Assert.IsTrue(tarefa.horaExecução > DateTime.Now.AddMinutes(-1));
}
Um Error aparece no lugar do Failed, com um ícone de warning (?!) e a mensagem “The agent process was stopped while the test was running”. Se clicarmos duas vezes em cima do Error para analisar o que aconteceu (ação já automática com a falha usual do assert), a mesma mensagem será exibida, o que não ajuda em nada (no caso do Failed vemos a stack trace).
Aí você descobre que deve clicar em “Test run error” para ver a stack trace, onde conseguirá a informação “One of the background threads threw exception: Microsoft.VisualStudio.TestTools.UnitTesting.AssertFailedException: Assert.Fail failed. Problema na validação”.
Isto é, por se tratar de uma thread em background, a maneira como o Visual Studio trata as verificações é diferente do comportamento usual, o que eu achei ruim. Isso mata alguém, não vejo maiores problemas, mas fico imaginando se trará algum impacto durante a automação dos testes ou em momentos diferentes, o que me deixa um pouco inseguro.
Outra coisa que eu tentei através do Visual Studio e não consegui ver uma maneira fácil (a não ser codificar), foi a possibilidade de executar o testes orientando o VS para disparar várias threads de uma só vez, me ajudando a testar condições de corrida.
Para ser super sincero, pode ser que o Visual Studio tenha recursos nessa área de testes para aplicações multi-thread, mas em algumas pesquisas rápidas que fiz não encontrei muitas referências e um funcionário da MS diz no fórum que “Nós não temos uma ótima história para contar sobre aplicações multi-threaded”. Em: http://social.msdn.microsoft.com/Forums/en-US/vststest/thread/c19e9ba8-52db-4970-99a4-04468206baf6/
Ainda vou explorar o DevCenter de computação paralela (http://msdn.microsoft.com/en-us/concurrency/bb895950.aspx) e o único recurso que eu achei para esse cenário, o projeto CHESS (http://research.microsoft.com/en-us/projects/chess/).
Para fechar o artigo: Com os computadores pessoais entrando na era dos multi-cores e todo mundo falando da necessidade de termos aplicações que aproveitem de verdade esses recursos (e até a GPU), espero que o Visual Studio traga boas novas nessa área e ajude os desenvolvedores, o que acredito que acontecerá.
Se você tiver alguma dica ou experiência para compartilhar, estou ansioso para ouvi-lo!
Baixe o PDF e código fonte aqui.
[]s
Luciano Caixeta Moreira - {Luti}
Chief Innovation Officer
Sr. Nimbus Serviços em Tecnologia Ltda
luciano.moreira@srnimbus.com.br
www.twitter.com/luticm
É muita coincidência. Alguém que tem o mesmo nome que o meu, mesmo apelido (Luti), mesma profissão, e ainda por cima trabalha com as mesmas ferramentas ;-) Só falta falar que você adora TDD ?
ResponderExcluir[]´s
Luciano C. Fernandes
luty@ufrj.br
lucianocfernandes@gmail.com
Gosto sim de TDD. Realmente muita coincidência, eita mundinho pequeno.
ResponderExcluirCara seus posts são muito interessantes. Pega meus contatos aí:
ResponderExcluirMSN: luty@UFRJ.br
Skype: lucianocfernandes
Twitter (já estou te followando :D ): luccasfer
GTalk: lucianocfernandes
[]´s
Luciano