Como fazer chamadas thread-safe para controles (Windows Forms .NET)
O multithreading pode melhorar o desempenho dos aplicativos do Windows Forms, mas o acesso aos controles do Windows Forms não é inerentemente seguro para threads. 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 um estado inconsistente e levar a condições de corrida, bloqueios e congelamentos ou travamentos. Se você implementar multithreading em seu aplicativo, certifique-se de chamar controles cross-thread de uma maneira thread-safe. Para obter mais informações, consulte Práticas recomendadas de threading gerenciado.
Importante
A documentação do Guia da Área de Trabalho para .NET 7 e .NET 6 está em construção.
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 para chamar um delegado criado no thread principal, que por sua vez chama o System.Windows.Forms.Control.Invoke controle. Ou, implemente um , que usa um System.ComponentModel.BackgroundWorkermodelo controlado por eventos para separar o trabalho feito no thread em segundo plano do relatório sobre os resultados.
Chamadas de thread cruzado inseguras
Não é seguro chamar um controle diretamente de um thread que não o criou. O trecho de código a seguir ilustra uma chamada não segura para o System.Windows.Forms.TextBox controle. O Button1_Click
manipulador de eventos cria um novo WriteTextUnsafe
thread, que define a propriedade do TextBox.Text thread principal diretamente.
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 de thread cruzado não válida. Controle acessado a partir de um thread diferente do thread em que foi criado. O InvalidOperationException sempre ocorre para chamadas de thread cruzado não seguras durante a depuração do Visual Studio e pode ocorrer no tempo de execução do aplicativo. Você deve corrigir o problema, mas pode desabilitar a exceção definindo a Control.CheckForIllegalCrossThreadCalls propriedade como false
.
Chamadas cross-thread seguras
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 System.Windows.Forms.Control.Invoke método, que chama um delegado do thread principal para chamar o controle.
- Um System.ComponentModel.BackgroundWorker componente, que oferece um modelo orientado a eventos.
Em ambos os exemplos, o thread em segundo plano fica em repouso 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 Windows Forms. Ele consulta a propriedade, que compara a ID de thread de criação do controle com a System.Windows.Forms.Control.InvokeRequired ID de thread de chamada. Se forem diferentes, você deve chamar o Control.Invoke método.
O WriteTextSafe
permite definir a TextBox propriedade do Text controle para um novo valor. O método consulta InvokeRequired. Se InvokeRequired retorna true
, recursivamente chama a si mesmo, WriteTextSafe
passando o método como um delegado para o Invoke método. Se InvokeRequired retornar false
, WriteTextSafe
define o TextBox.Text diretamente. O Button1_Click
manipulador de eventos cria o novo thread e executa o WriteTextSafe
método.
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 multithreading é com o System.ComponentModel.BackgroundWorker componente, que usa um modelo orientado a eventos. O thread em segundo plano gera o evento, que não interage com o BackgroundWorker.DoWork thread principal. O thread principal executa os manipuladores de eventos e BackgroundWorker.RunWorkerCompleted , que podem chamar os BackgroundWorker.ProgressChanged controles do thread principal.
Para fazer uma chamada thread-safe usando BackgroundWorker, manipule o DoWork evento. Há dois eventos que o trabalhador em segundo plano usa para relatar o status: ProgressChanged e RunWorkerCompleted. O ProgressChanged
evento é usado para comunicar atualizações de status ao thread principal e o evento é usado para sinalizar que o RunWorkerCompleted
trabalhador 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, pausando por um segundo entre as DoWork
contagens. Ele usa o manipulador de eventos para relatar o ProgressChanged número de volta ao thread principal e definir a TextBox propriedade do Text controle. Para que o ProgressChanged evento funcione, a BackgroundWorker.WorkerReportsProgress propriedade 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
Comentários
https://aka.ms/ContentUserFeedback.
Em breve: Ao longo de 2024, eliminaremos os problemas do GitHub como o mecanismo de comentários para conteúdo e o substituiremos por um novo sistema de comentários. Para obter mais informações, consulteEnviar e exibir comentários de