.NET – Evitando o InvalidOperationException em aplicações Multi Thread Windows Forms

Um problema um pouco comum para quem trabalha com aplicações Windows Forms e rotinas Multi Thread, principalmente quando as rotinas multi thread interagem com os controles. Vamos imaginar uma situação típica, um aplicativo de envio de emails.

Para oferecer uma interface amigável, você expõem campos com valores editáveis, enfim, e no final do formulário, um TextBox Multiline para mostrar o andamento das tarefas, como se fosse uma janela de Output do Visual Studio. Durante a sua rotina, você quer inserir informações do estado da execução do seu algoritmo. Bem, nada mais simples do que adicionar texto ao TextBox da forma mais tradicional possível.

Bom, se fizer isso sem aplicar Multi Thread, o que você verá é a tela parada até o término da execução da rotina, o que não serve para nós. O jeito é executar a mesma em outra thread, certo? Essa é a parte fácil, já que o .NET Framework dispõem de métodos e bibliotecas bem simples para isso. Você põem para executar o aplicativo já rodando sua rotina principal em outra thread e: InvalidOperationException.

A razão do erro é simples, mas nem tanto. Todos os controles Windows são por padrão Thread-Safe. Objetos Thread-Safe só podem ser manipulados por uma thread por vez. Isso evita que estes sejam colocados em estados inválidos ou inconsistentes. São vários os casos, mas se está tendo este problema, provavelmente sabe do que estou falando.

O que é preciso fazer então? Garantir que sua thread acessa o controle de forma que não interfira em outras threads que já o estejam manipulando. Para isso, precisaremos da propriedade InvokeRequired do controle, o método Invoke e o um delegate para criar um CallBack da nossa função. Bom, nada melhor que um exemplo para melhor entender. Vou economizar tempo e usar um da própria Microsoft, com todas as explicações nos comentários:

// Delegate para permitir a chamada assíncrona
// ao método setText
delegate void SetTextCallback(string text);
 
// A thread a ser executada
private Thread demoThread = null;
 
// O evento que segue cria um nova Thread
// para execução do método ThreadProcSafe
private void setTextSafeBtn_Click(object sender, EventArgs e)
{
	this.demoThread =
		new Thread(new ThreadStart(this.ThreadProcSafe));
 
	this.demoThread.Start();
}
 
// Método que faz uma chamada ao SetText
// que faz a verificação de Thread-Safe
private void ThreadProcSafe()
{
	this.SetText("Texto do textbox.");
}
 
// Método que tenta atribuir o texto ao controle textbox1
// de forma "segura"
private void SetText(string text)
{
	// a chamada ao InvokeRequired garante que a thread atual
	// é a mesma que está manipulando o controle no momento
	// Caso não seja a mesma thread,
	// retorna true e faz a chamada ao CallBack.
	// a chamada ao metódo Invoke faz com que a thread
	// que manipula o componente chame o CallBack quando
	// liberar o controle, passando os argumentos
	if (this.textBox1.InvokeRequired)
	{
		SetTextCallback d = new SetTextCallback(SetText);
		this.Invoke(d, new object[] { text });
	}
	else
	{
		this.textBox1.Text = text;
	}
}

Em miúdos, ao tentar alterar a propriedade de texto do controle este já estiver sendo manipulado por outra thread, cria-se um enfileiramento da sua chamada. Assim, quando a thread que ocupa o controle liberá-lo, sua rotina será automaticamente executada.

A parte chata é que isso deverá ser feito para toda e qualquer propriedade que precisar ser alterada no controle sob a execução de um thread que não a principal.

O mesmo exemplo pode ser visto em VB.NET neste link:

http://www.codigofonte.net/dicas/dotnet/222_trabalhando-com-threads-em-formularios-windows

Bom proveito!

Referências:

http://msdn.microsoft.com/en-us/library/ms171728.aspx

Comentários

comentários