Nota
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
Multithreading puede mejorar el rendimiento de las aplicaciones de Windows Forms, pero el acceso a los controles de Windows Forms no es inherentemente seguro para subprocesos. Multithreading puede exponer el código a errores graves y complejos. Dos o más subprocesos que manipulan un control pueden forzar el mismo a un estado incoherente y provocar condiciones de carrera, interbloqueos y congelaciones. Si implementa multihilo en su aplicación, asegúrese de controlar acciones entre hilos de manera segura. Para obtener más información, consulte prácticas recomendadas de hilos administrados.
Hay dos maneras de llamar de forma segura a un control de Windows Forms desde un subproceso que no creó ese control. Use el System.Windows.Forms.Control.Invoke método para llamar a un delegado creado en el subproceso principal, que a su vez llama al control. O bien, implemente un System.ComponentModel.BackgroundWorker, que usa un modelo controlado por eventos para separar el trabajo realizado en el subproceso en segundo plano de informar sobre los resultados.
Llamadas no seguras entre subprocesos
No es seguro llamar a un control directamente desde un subproceso que no lo creó. En el fragmento de código siguiente se muestra una llamada no segura al System.Windows.Forms.TextBox control . El Button1_Click
controlador de eventos crea un nuevo WriteTextUnsafe
subproceso, que establece directamente la propiedad del TextBox.Text subproceso 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
El depurador de Visual Studio detecta estas llamadas a subprocesos no seguras al generar un InvalidOperationException con el mensaje Operación entre subprocesos no válida. Control accesado desde un subproceso diferente al en el que fue creado. El InvalidOperationException siempre se produce para las llamadas entre subprocesos no seguras durante la depuración de Visual Studio y puede ocurrir también en el tiempo de ejecución de la aplicación. Debe corregir el problema, pero puede deshabilitar la excepción estableciendo la propiedad Control.CheckForIllegalCrossThreadCalls en false
.
Llamadas seguras a través de hilos
En los ejemplos de código siguientes se muestran dos maneras de llamar de forma segura a un control de Windows Forms desde un subproceso que no lo creó:
- Método System.Windows.Forms.Control.Invoke, que llama a un delegado desde el hilo principal para invocar al control.
- Un System.ComponentModel.BackgroundWorker componente, que ofrece un modelo controlado por eventos.
En ambos ejemplos, el subproceso en segundo plano se suspende durante un segundo para simular el trabajo que se realiza en ese subproceso.
Ejemplo: Uso del método Invoke
En el ejemplo siguiente se muestra un patrón para garantizar llamadas seguras para subprocesos a un control de Windows Forms. Consulta la System.Windows.Forms.Control.InvokeRequired propiedad, que compara el identificador de subproceso de creación del control con el identificador de subproceso que hace la llamada. Si son diferentes, debería llamar al método Control.Invoke.
WriteTextSafe
permite establecer un nuevo valor para la propiedad TextBox del control Text. El método consulta InvokeRequired. Si InvokeRequired devuelve true
, WriteTextSafe
se llama recursivamente a sí mismo, pasando el método como delegado al Invoke método . Si InvokeRequired devuelve false
, WriteTextSafe
establece directamente TextBox.Text . El Button1_Click
controlador de eventos crea el nuevo subproceso y ejecuta el 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
Ejemplo: Usar un BackgroundWorker
Una manera fácil de implementar multithreading es con el System.ComponentModel.BackgroundWorker componente , que usa un modelo controlado por eventos. El subproceso en segundo plano genera el BackgroundWorker.DoWork evento , que no interactúa con el subproceso principal. El hilo principal ejecuta los controladores de eventos BackgroundWorker.ProgressChanged y BackgroundWorker.RunWorkerCompleted, los cuales pueden llamar a los controles del hilo principal.
Para realizar una llamada segura para subprocesos mediante BackgroundWorker, controle el evento DoWork. Hay dos eventos que utiliza el trabajador en segundo plano para notificar el estado: ProgressChanged y RunWorkerCompleted. El evento ProgressChanged
se usa para comunicar las actualizaciones de estado al hilo principal, y el evento RunWorkerCompleted
se usa para indicar que el trabajador en segundo plano ha terminado su trabajo. Para iniciar el subproceso en segundo plano, llame a BackgroundWorker.RunWorkerAsync.
El ejemplo cuenta de 0 a 10 durante el evento DoWork
, pausando durante un segundo entre cuentas. Utiliza el ProgressChanged controlador de eventos para comunicar el número de regreso al subproceso principal y establecer la propiedad TextBox del control Text. Para que el evento ProgressChanged funcione, la propiedad BackgroundWorker.WorkerReportsProgress debe establecerse en 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