C# -
Atualizando a Interface a partir de outra thread - I
![]() |
Neste artigo veremos como atualizar os elementos da interface no Windows Forms a partir de outra thread. |
Em um aplicativo Windows Forms, os elementos da interface do usuário são criados e atualizados pela thread principal. Essa thread também é responsável pelo processamento das mensagens do Windows.
Por esse
motivo, é recomendável manter o processamento das mensagens curto e simples.
Operações de longa duração executadas nesta thread farão com que as mensagens
recebidas do Windows sejam colocadas na fila. Se as mensagens não forem
processadas em tempo hábil, o aplicativo vai parecer 'congelado' ou
suspenso e fornecerá uma experiência ruim ao usuário.
Para operações de longa execução, você pode descarregar o trabalho em threads em
segundo plano. No entanto, a atualização dos elementos da interface do usuário a
partir de threads diferentes daquelas que as criaram resultará no lançamento de
uma exceção.
Isso ocorre porque os elementos da interface do usuário podem ser atualizados apenas a partir das threads que os criaram. O acesso aos controles do Windows Forms não é seguro para threads. Se você tiver duas ou mais threads manipulando o estado de um controle, é possível forçar o controle para um estado inconsistente. Outros erros relacionados ao encadeamento são possíveis, como condições de corrida e deadlocks. É importante garantir que o acesso aos seus controles seja realizado de forma segura para threads.
Existem duas formas de chamar com segurança um controle Windows Forms de uma thread que não criou esse controle.
1- Você pode usar o método System.Windows.Forms.Control.Invoke para chamar um delegate criado no thread principal, que por sua vez chama o controle;
2- Ou você pode implementar um System.ComponentModel.BackgroundWorker, que usa um modelo orientado a eventos para separar o trabalho realizado na thread em segundo plano dos relatórios sobre os resultados.
Realizando chamadas inseguras entre as threads
Não é seguro chamar um controle diretamente de uma thread que não o criou. Vejamos um exemplo básico.
Vamos criar um projeto Windows Forms para .NET Framework chamado WF_ThreadSegura1:
No formulário Form1.cs vamos incluir um controle TextBox e um controle Button.
O código a seguir ilustra uma chamada não segura para o controle System.Windows.Forms.TextBox. O manipulador de eventos Button1_Click cria uma nova thread EscreveTextoThreadInsegura, que define diretamente a propriedade TextBox.Text da thread principal :
Ao executar este código iremos obter o seguinte resultado:
O depurador do Visual Studio detecta essas chamadas de segmento não seguras, gerando uma InvalidOperationException com a mensagem : 'Operação entre segmentos inválida. Controle "" acessado a partir de um thread diferente daquele em que foi criado....'
O erro InvalidOperationException sempre ocorre para chamadas de segmento cruzado inseguras durante a depuração do Visual Studio e pode ocorrer no runtime do aplicativo. Você deve corrigir o problema, mas pode desativar a exceção definindo a propriedade Control.CheckForIllegalCrossThreadCalls como false.
Assim podemos desativar a exceção no exemplo assim:
E agora ao executar novamente o projeto não teremos o lançamento da exceção. Mas essa não é a melhor abordagem para resolver esse problema pois isso nem sempre vai funcionar.
Então vejamos como fazer isso de forma segura e aderente ás boas práticas.
Usando o método Invoke com um delegate
Neste exemplo temos um padrão para garantir chamadas seguras de thread para um controle Windows Forms.
Primeiro consultamos a propriedade System.Windows.Forms.Control.InvokeRequired, que compara o ID da thread de criação do controle com o ID da thread de chamada.
Se os IDs das threads forem iguais, ele chama o controle diretamente. Se os IDs das threads forem diferentes, ele chamará o método Control.Invoke com um delegate da thread principal, que faz a chamada real para o controle.
O delegate DelegateChamadaSegura permite definir a propriedade Text do controle TextBox. O método EscreveTextoThreadSegura consulta a propriedade InvokeRequired.
Se InvokeRequired retornar true, EscreveTextoThreadSegura passa o delegate DelegateChamadaSegura para o método Invoke para fazer a chamada real ao controle.
Se InvokeRequired retornar false, EscreveTextoSeguro define o TextBox.Text diretamente. O manipulador de eventos Button1_Click cria a nova thread e executa o método EscreveTextEscreveTextoThreadSegura oSeguro.
Na segunda parte do artigo veremos como usar um manipulador de eventos BackGroundWorker.
Pegue o
projeto aqui:
WF_ThreadSegura1.zip
"(Disse
Jesus)Passará o céu e a terra, mas as minhas palavras não hão de passar."
Lucas 21:33
Referências:
Super DVD Vídeo Aulas - Vídeo Aula sobre VB .NET, ASP .NET e C#
NET - O conceito de Delegate revisitado - Macoratti
C# - Usando delegates na prática - Ordenação - Macoratti
C# - Compreendendo delegates (revisitado) - Macoratti
C# - Delegates - Macoratti
C# - Eventos (revisitado) - Macoratti
C# - Eventos - Macoratti