Sep 30, 2006

O que esta errado com esta código (Parte X)

Infelizmente, por motivo de tempo, já faz algum tempo que não posto neste blog :(. Infelizmente ao postar a última entrada desta série (Oque esta errado com este código?) não inclui um número. Assim sendo, vou atribuir o valor X para o mesmo. Com relação ao post referido, o problema esta relacionado ao acesso de propriedades de controles da UI em uma thread diferente daquela que o componente.

Observe novamente o código abaixo:

Created with colorer-take5 library. Type 'csharp'

0: using System;
1: using System.Windows.Forms;
2: using System.Threading;
3:
4: namespace TestWinForm
5: {
6:     public partial class TestCallBackFromDiferentThread : Form
7:     {
8:         private System.Threading.Thread workerThread;
9:
10:         public TestCallBackFromDiferentThread()
11:         {
12:             InitializeComponent();
13:         }
14:
15:         private void button1_Click(object sender, EventArgs e)
16:         {
17:             ThreadStart ts = new ThreadStart(AddNewLine);
18:             workerThread = new Thread(ts);
19:             workerThread.Start();
20:         }
21:
22:         private void AddNewLine()
23:         {
24:             listBox1.Items.Add(textBox1.Text);
25:         }
26:     }
27: }

Nas linhas # 17 ~ 19 criamos uma thread e iniciamos a execução da mesma; esta thread chama a função AddNewLine() que por sua vez tenta incluir um item em uma listbox. O problema (como explicado aqui) é que o acesso a controles em WinForms não são thread safe automaticamente, ou seja, se duas threads modificarem o estado de um controle simultaneamente o mesmo pode ficar em um estado inconsistente ou mesmo gerar exceções (oque seria melhor que a primeira possibilidade). Crie uma aplicação WinForm, adicionar os controles listbox, textbox e um botão e copie o código acima para o formulário da aplicação. Se você executar esta aplicação a partir do explorer pode lhe parecer que a mesma não possui problema nenhum; ou seja, aparentemente a mesma funcionou. Contudo execute a mesma a partir do Visual Studio; se a opção "enable exception assistence" estiver habilitada uma exceção será gerada quando a thread tentar acessar o listbox na linha 24. Ok, e como solucionar o problema? Simples, usando a propriedade InvokeRequerid do controle... No método AddNewLine(), antes de incluir o item no listbox consulte a propriedade InvokeRequired; se a mesma for true então execute o método Invoke() passando um delegate que irá executar o mesmo método (AddNewLine()).

Veja no exemplo abaixo:

Created with colorer-take5 library. Type 'csharp'

using System;
using System.Windows.Forms;
using System.Threading;

namespace TestWinForm
{
    public partial class TestCallBackFromDiferentThread : Form
    {
        private System.Threading.Thread workerThread;

         public TestCallBackFromDiferentThread()
         {
             InitializeComponent();
         }

         private void button1_Click(object sender, EventArgs e)
         {
             ThreadStart ts = new ThreadStart(AddNewLine);
             workerThread = new Thread(ts);
             workerThread.Start();
         }

         private void AddNewLine()
         {
             if (listBox1.InvokeRequired)
             {
                listbox1.Invoke(new MethodInvoker(AddNewLine));
             }
             else
             {
                listBox1.Items.Add(textBox1.Text);
             }
         }
     }
 }

Você pode encontrar mais informações (em inglês) sobre o assunto aqui.

A partir do próximo post irei iniciar uma série onde em cada post falarei sobre um aplicativo/site que uso com frequência e que acho interessante. Fique atento.

Adriano