Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
O multithreading pode aprimorar o desempenho de aplicativos do Windows Forms, mas o acesso aos controles do Windows Forms não é inerentemente thread-safe. O multithreading pode expor seu código a bugs sérios e complexos. Dois ou mais threads manipulando um controle podem forçar o controle a ficar em um estado inconsistente, levando a condições de disputa, deadlocks, congelamentos ou travamentos. Se você implementar o multithreading em seu aplicativo, certifique-se de chamar controles entre threads de maneira segura. Para mais informações, confira Práticas recomendadas de encadeamento gerenciado.
Há duas maneiras de chamar com segurança um controle do Windows Forms de um thread que não criou esse controle. Use o método System.Windows.Forms.Control.Invoke para chamar um delegado criado no thread principal, que, por sua vez, chama o controle. Ou implemente um System.ComponentModel.BackgroundWorker, que usa um modelo controlado por eventos para separar o trabalho feito no thread em segundo plano de relatórios sobre os resultados.
Chamadas entre threads não seguras
Não é seguro chamar um controle diretamente de um thread que não o criou. O snippet de código a seguir ilustra uma chamada não segura para o controle System.Windows.Forms.TextBox. O manipulador de eventos Button1_Click
cria um novo thread de WriteTextUnsafe
, que define diretamente a propriedade TextBox.Text do thread principal.
private void button1_Click(object sender, EventArgs e)
{
var thread2 = new System.Threading.Thread(WriteTextUnsafe);
thread2.Start();
}
private void WriteTextUnsafe() =>
textBox1.Text = "This text was set unsafely.";
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim thread2 As New System.Threading.Thread(AddressOf WriteTextUnsafe)
thread2.Start()
End Sub
Private Sub WriteTextUnsafe()
TextBox1.Text = "This text was set unsafely."
End Sub
O depurador do Visual Studio detecta essas chamadas de thread não seguras gerando uma InvalidOperationException com a mensagem Operação entre threads não é válida. O controle foi acessado a partir de um thread diferente daquele em que ele foi criado. O InvalidOperationException sempre ocorre para chamadas entre threads não seguras durante a depuração do Visual Studio e pode ocorrer em tempo de execução do aplicativo. Você deve corrigir o problema, mas pode desabilitar a exceção definindo a propriedade Control.CheckForIllegalCrossThreadCalls como false
.
Chamadas seguras entre threads
Os exemplos de código a seguir demonstram duas maneiras de chamar com segurança um controle do Windows Forms de um thread que não o criou:
- O método System.Windows.Forms.Control.Invoke, que chama um delegado do thread principal para invocar o controle.
- Um componente System.ComponentModel.BackgroundWorker, que oferece um modelo controlado por eventos.
Em ambos os exemplos, o thread de plano de fundo fica inativo por um segundo para simular o trabalho que está sendo feito nesse thread.
Exemplo: usar o método Invoke
O exemplo a seguir demonstra um padrão para garantir chamadas thread-safe para um controle do Windows Forms. Ele consulta a propriedade System.Windows.Forms.Control.InvokeRequired, que compara o ID do thread que criou o controle com o ID do thread que está realizando a chamada. Se eles forem diferentes, você deverá chamar o método Control.Invoke.
O WriteTextSafe
permite definir a propriedade TextBox do controle Text como um novo valor. O método consulta InvokeRequired. Se InvokeRequired retornar true
, WriteTextSafe
chamará a si mesmo recursivamente, passando o método como um delegado para o método Invoke. Se InvokeRequired retornar false
, WriteTextSafe
definirá o TextBox.Text diretamente. O manipulador de eventos Button1_Click
cria o novo thread e executa o método WriteTextSafe
.
private void button1_Click(object sender, EventArgs e)
{
var threadParameters = new System.Threading.ThreadStart(delegate { WriteTextSafe("This text was set safely."); });
var thread2 = new System.Threading.Thread(threadParameters);
thread2.Start();
}
public void WriteTextSafe(string text)
{
if (textBox1.InvokeRequired)
{
// Call this same method but append THREAD2 to the text
Action safeWrite = delegate { WriteTextSafe($"{text} (THREAD2)"); };
textBox1.Invoke(safeWrite);
}
else
textBox1.Text = text;
}
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim threadParameters As New System.Threading.ThreadStart(Sub()
WriteTextSafe("This text was set safely.")
End Sub)
Dim thread2 As New System.Threading.Thread(threadParameters)
thread2.Start()
End Sub
Private Sub WriteTextSafe(text As String)
If (TextBox1.InvokeRequired) Then
TextBox1.Invoke(Sub()
WriteTextSafe($"{text} (THREAD2)")
End Sub)
Else
TextBox1.Text = text
End If
End Sub
Exemplo: usar um BackgroundWorker
Uma maneira fácil de implementar o multithreading é com o componente System.ComponentModel.BackgroundWorker, que usa um modelo controlado por eventos. O thread em segundo plano gera o evento BackgroundWorker.DoWork, que não interage com o thread principal. O thread principal executa os manipuladores de eventos BackgroundWorker.ProgressChanged e BackgroundWorker.RunWorkerCompleted, que podem chamar os controles do thread principal.
Para fazer uma chamada thread-safe usando BackgroundWorker, manipule o evento DoWork. Há dois eventos que o trabalho em segundo plano usa para relatar o status: ProgressChanged e RunWorkerCompleted. O evento ProgressChanged
é usado para comunicar atualizações de status para o thread principal e o evento RunWorkerCompleted
é usado para sinalizar que o trabalho em segundo plano concluiu seu trabalho. Para iniciar o thread em segundo plano, chame BackgroundWorker.RunWorkerAsync.
O exemplo conta de 0 a 10 no evento DoWork
, pausando por um segundo entre cada contagem. Ele usa o manipulador de eventos ProgressChanged para reportar o número de volta ao thread principal e para definir a propriedade TextBox do controle Text. Para que o evento ProgressChanged funcione, a propriedade BackgroundWorker.WorkerReportsProgress deve ser definida como true
.
private void button1_Click(object sender, EventArgs e)
{
if (!backgroundWorker1.IsBusy)
backgroundWorker1.RunWorkerAsync();
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
int counter = 0;
int max = 10;
while (counter <= max)
{
backgroundWorker1.ReportProgress(0, counter.ToString());
System.Threading.Thread.Sleep(1000);
counter++;
}
}
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e) =>
textBox1.Text = (string)e.UserState;
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
If (Not BackgroundWorker1.IsBusy) Then
BackgroundWorker1.RunWorkerAsync()
End If
End Sub
Private Sub BackgroundWorker1_DoWork(sender As Object, e As ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
Dim counter = 0
Dim max = 10
While counter <= max
BackgroundWorker1.ReportProgress(0, counter.ToString())
System.Threading.Thread.Sleep(1000)
counter += 1
End While
End Sub
Private Sub BackgroundWorker1_ProgressChanged(sender As Object, e As ComponentModel.ProgressChangedEventArgs) Handles BackgroundWorker1.ProgressChanged
TextBox1.Text = e.UserState
End Sub
.NET Desktop feedback