Em aplicações Windows Forms com C#, a atualização segura de controles de interface do usuário a partir de threads secundários requer o uso dos métodos Invoke e BeginInvoke. Estes métodos pertencem à classe Control e permitem a execução de delegados de forma controlada na thread principal.
Distinção Fundamental antre Invoke e BeginInvoke
O método Control.Invoke executa um delegado de maneira síncrona no thread que detém o handle da janela do controle. A thread que aciona o Invoke fica bloqueada até a conclusão da execução do delegado.
Já o Control.BeginInvoke agenda um delegado para execução assíncrona no thread que criou o handle do controle. A thread chamadora prossegue imediatamente, sem aguardar a finalização do delegado.
Portanto, Invoke proporciona sincronia em relação à thread de trabalho, enquanto BeginInvoke oferece assincronia, mantendo a execução do código na thread de IU sempre segura.
Quando Utilizar Cada Método
Opte por Invoke quendo a thread de trabalho necessitar atualizar a interface e precisar esperar que essa atualização seja concluída para continuar o fluxo.
Empregue BeginInvoke se a thread de trabalho puder avançar imediatamente após o agendamento da atualização da interface, sem necessidade de sincronização imediata.
Exemplo com Execução Síncrona (Invoke)
O código abaixo demonstra o uso de Invoke, onde a thread de trabalho aguarda a conclusão da atualização na interface:
private void AoClicarBotao(object sender, EventArgs args) { MessageBox.Show("ID da thread principal: " + Thread.CurrentThread.ManagedThreadId);
Thread worker = new Thread(ProcessarDados);
worker.Start();
for (int cont = 0; cont < 3; cont++)
{
Thread.Sleep(400);
}
}
private void ProcessarDados() { MessageBox.Show("ID da thread de trabalho: " + Thread.CurrentThread.ManagedThreadId);
Invoke(new Action(() =>
{
meuBotao.Text = "Carregado";
MessageBox.Show("Delegado executado na thread: " + Thread.CurrentThread.ManagedThreadId);
}));
MessageBox.Show("Thread de trabalho finalizou: " + Thread.CurrentThread.ManagedThreadId);
}
</div>Neste caso, a thread de trabalho é suspensa até que a ação dentro de Invoke seja concluída na thread principal.
Exemplo com Execução Assíncrona (BeginInvoke)
---------------------------------------------
Utilizando BeginInvoke para operação não bloqueante:
<div>```
private void AoClicarBotao(object sender, EventArgs args)
{
MessageBox.Show("ID da thread principal: " + Thread.CurrentThread.ManagedThreadId);
Thread worker = new Thread(ProcessarDados);
worker.Start();
for (int cont = 0; cont < 3; cont++)
{
Thread.Sleep(400);
}
}
private void ProcessarDados()
{
MessageBox.Show("ID da thread de trabalho: " + Thread.CurrentThread.ManagedThreadId);
BeginInvoke(new Action(() =>
{
meuBotao.Text = "Carregado";
MessageBox.Show("Delegado executado na thread: " + Thread.CurrentThread.ManagedThreadId);
}));
MessageBox.Show("Thread de trabalho continuou: " + Thread.CurrentThread.ManagedThreadId);
}
Solução para Acesso Cruzado de Threads
Em programação multithread, acessar diretamente propriedades de controles de IU de threads exetrnas resulta em exceções. A utilização de Invoke ou BeginInvoke garante que o código de atualização seja executado na thread correta.
Uma abordagem comum encapsula a lógica de atualização em um método que verifica a necessidade de invocação:
private void DefinirTextoControle(string texto) { if (caixaTexto.InvokeRequired) { Invoke(new Action(() => caixaTexto.Text = texto)); } else { caixaTexto.Text = texto; } }
</div>Técnicas Adicionais e Simplificações
------------------------------------
Embora seja possível desativar as verificações de thread com a propriedade `Control.CheckForIllegalCrossThreadCalls`, isso não é recomendado por comprometer a segurança de concorrência.
Expressões lambda em versões recentes do C# permitem uma sintaxe mais concisa para delegados:
<div>```
private void AtualizarInterface()
{
Invoke(() =>
{
meuLabel.Text = "Valor atualizado";
});
}