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