Procedimiento Realizar llamadas seguras para subprocesos a controles de Windows FormsHow to: Make thread-safe calls to Windows Forms controls

El multithreading puede mejorar el rendimiento de las aplicaciones Windows Forms, pero el acceso a los controles Windows Forms no es seguro para subprocesos de forma inherente.Multithreading can improve the performance of Windows Forms apps, but access to Windows Forms controls isn't inherently thread-safe. El multithreading puede exponer el código a errores muy graves y complejos.Multithreading can expose your code to very serious and complex bugs. Dos o más subprocesos que manipulan un control pueden forzar el control en un estado incoherente y conducir a condiciones de carrera, interbloqueos e inmovilizaciones o bloqueos.Two or more threads manipulating a control can force the control into an inconsistent state and lead to race conditions, deadlocks, and freezes or hangs. Si implementa multithreading en la aplicación, asegúrese de llamar a los controles entre subprocesos de una manera segura para subprocesos.If you implement multithreading in your app, be sure to call cross-thread controls in a thread-safe way. Para obtener más información, vea procedimientos recomendados para el subprocesamiento administrado.For more information, see Managed threading best practices.

Hay dos maneras de llamar a un control de Windows Forms de forma segura desde un subproceso que no creó ese control.There are two ways to safely call a Windows Forms control from a thread that didn't create that control. Puede usar 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.You can use the System.Windows.Forms.Control.Invoke method to call a delegate created in the main thread, which in turn calls the control. O bien, puede implementar un System.ComponentModel.BackgroundWorker, que utiliza un modelo orientado a eventos para separar el trabajo realizado en el subproceso en segundo plano de los informes sobre los resultados.Or, you can implement a System.ComponentModel.BackgroundWorker, which uses an event-driven model to separate work done in the background thread from reporting on the results.

Llamadas no seguras entre subprocesosUnsafe cross-thread calls

No es seguro llamar a un control directamente desde un subproceso que no lo creó.It's unsafe to call a control directly from a thread that didn't create it. En el fragmento de código siguiente se muestra una llamada no segura System.Windows.Forms.TextBox al control.The following code snippet illustrates an unsafe call to the System.Windows.Forms.TextBox control. El Button1_Click controlador de eventos crea un WriteTextUnsafe nuevo subproceso, que establece la propiedad TextBox.Text del subproceso principal directamente.The Button1_Click event handler creates a new WriteTextUnsafe thread, which sets the main thread's TextBox.Text property directly.

private void Button1_Click(object sender, EventArgs e)
{
    thread2 = new Thread(new ThreadStart(WriteTextUnsafe));
    thread2.Start();
}
private void WriteTextUnsafe()
{
    textBox1.Text = "This text was set unsafely.";
}
Private Sub Button1_Click(ByVal sender As Object, e As EventArgs) Handles Button1.Click
    Thread2 = New Thread(New ThreadStart(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 de subprocesos no InvalidOperationException seguras mediante la generación de un con el mensaje, la operación entre subprocesos no es válida. Control "" al que se tiene acceso desde un subproceso distinto del subproceso en el que se creó.The Visual Studio debugger detects these unsafe thread calls by raising an InvalidOperationException with the message, Cross-thread operation not valid. Control "" accessed from a thread other than the thread it was created on. InvalidOperationException Siempre se produce para llamadas no seguras entre subprocesos durante la depuración de Visual Studio y puede producirse en el tiempo de ejecución de la aplicación.The InvalidOperationException always occurs for unsafe cross-thread calls during Visual Studio debugging, and may occur at app runtime. Debe corregir el problema, pero puede deshabilitar la excepción estableciendo la Control.CheckForIllegalCrossThreadCalls propiedad en. falseYou should fix the issue, but you can disable the exception by setting the Control.CheckForIllegalCrossThreadCalls property to false.

Llamadas seguras entre subprocesosSafe cross-thread calls

En los siguientes ejemplos de código se muestran dos maneras de llamar a un control de Windows Forms de forma segura desde un subproceso que no lo creó:The following code examples demonstrate two ways to safely call a Windows Forms control from a thread that didn't create it:

  1. El System.Windows.Forms.Control.Invoke método, que llama a un delegado del subproceso principal para llamar al control.The System.Windows.Forms.Control.Invoke method, which calls a delegate from the main thread to call the control.
  2. Un System.ComponentModel.BackgroundWorker componente, que ofrece un modelo orientado a eventos.A System.ComponentModel.BackgroundWorker component, which offers an event-driven model.

En ambos ejemplos, el subproceso en segundo plano se suspende durante un segundo para simular el trabajo que se realiza en ese subproceso.In both examples, the background thread sleeps for one second to simulate work being done in that thread.

Puede compilar y ejecutar estos ejemplos como aplicaciones .NET Framework desde C# la línea de comandos de o Visual Basic.You can build and run these examples as .NET Framework apps from the C# or Visual Basic command line. Para obtener más información, vea compilar desde la línea de comandos con CSC. exe o compilar desde la línea de comandos (Visual Basic).For more information, see Command-line building with csc.exe or Build from the command line (Visual Basic).

A partir de .net Core 3,0, también puede compilar y ejecutar los ejemplos como aplicaciones Windows .net Core desde una carpeta que tiene un <nombre de carpeta Windows Forms de .net Core > archivo de proyecto. csproj.Starting with .NET Core 3.0, you can also build and run the examples as Windows .NET Core apps from a folder that has a .NET Core Windows Forms <folder name>.csproj project file.

Ejemplo: Usar el método Invoke con un delegadoExample: Use the Invoke method with a delegate

En el ejemplo siguiente se muestra un patrón para garantizar las llamadas seguras para subprocesos a un control de Windows Forms.The following example demonstrates a pattern for ensuring thread-safe calls to a Windows Forms control. Consulta la System.Windows.Forms.Control.InvokeRequired propiedad, que compara el identificador del subproceso de creación del control con el identificador del subproceso que realiza la llamada.It queries the System.Windows.Forms.Control.InvokeRequired property, which compares the control's creating thread ID to the calling thread ID. Si los identificadores de subproceso son iguales, llama directamente al control.If the thread IDs are the same, it calls the control directly. Si los identificadores de subproceso son diferentes, Control.Invoke llama al método con un delegado del subproceso principal, que realiza la llamada real al control.If the thread IDs are different, it calls the Control.Invoke method with a delegate from the main thread, which makes the actual call to the control.

Habilita el establecimiento de TextBox la propiedad Text del control. SafeCallDelegateThe SafeCallDelegate enables setting the TextBox control's Text property. El WriteTextSafe método consulta InvokeRequired.The WriteTextSafe method queries InvokeRequired. Si InvokeRequired devuelve true ,WriteTextSafe pasa almétodoparahacerlallamadarealalcontrol.SafeCallDelegate InvokeIf InvokeRequired returns true, WriteTextSafe passes the SafeCallDelegate to the Invoke method to make the actual call to the control. Si InvokeRequired devuelve false, WriteTextSafe establece directamente.TextBox.TextIf InvokeRequired returns false, WriteTextSafe sets the TextBox.Text directly. El Button1_Click controlador de eventos crea el nuevo subproceso y WriteTextSafe ejecuta el método.The Button1_Click event handler creates the new thread and runs the WriteTextSafe method.

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

public class InvokeThreadSafeForm : Form
{
    private delegate void SafeCallDelegate(string text);
    private Button button1;
    private TextBox textBox1;
    private Thread thread2 = null;

    [STAThread]
    static void Main()
    {
        Application.SetCompatibleTextRenderingDefault(false);
        Application.EnableVisualStyles();
        Application.Run(new InvokeThreadSafeForm());
    }
    public InvokeThreadSafeForm()
    {
        button1 = new Button
        {
            Location = new Point(15, 55),
            Size = new Size(240, 20),
            Text = "Set text safely"
        };
        button1.Click += new EventHandler(Button1_Click);
        textBox1 = new TextBox
        {
            Location = new Point(15, 15),
            Size = new Size(240, 20)
        };
        Controls.Add(button1);
        Controls.Add(textBox1);
    }

    private void Button1_Click(object sender, EventArgs e)
    {
        thread2 = new Thread(new ThreadStart(SetText));
        thread2.Start();
        Thread.Sleep(1000);
    }

    private void WriteTextSafe(string text)
    {
        if (textBox1.InvokeRequired)
        {
            var d = new SafeCallDelegate(WriteTextSafe);
            textBox1.Invoke(d, new object[] { text });
        }
        else
        {
            textBox1.Text = text;
        }
    }

    private void SetText()
    {
        WriteTextSafe("This text was set safely.");
    }
}
Imports System.Drawing
Imports System.Threading
Imports System.Windows.Forms

Public Class InvokeThreadSafeForm : Inherits Form

    Public Shared Sub Main()
        Application.SetCompatibleTextRenderingDefault(False)
        Application.EnableVisualStyles()
        Dim frm As New InvokeThreadSafeForm()
        Application.Run(frm)
    End Sub

    Dim WithEvents Button1 As Button
    Dim TextBox1 As TextBox
    Dim Thread2 as Thread = Nothing

    Delegate Sub SafeCallDelegate(text As String)

    Private Sub New()
        Button1 = New Button()
        With Button1
            .Location = New Point(15, 55)
            .Size = New Size(240, 20)
            .Text = "Set text safely"
        End With
        TextBox1 = New TextBox()
        With TextBox1
            .Location = New Point(15, 15)
            .Size = New Size(240, 20)
        End With
        Controls.Add(Button1)
        Controls.Add(TextBox1)
    End Sub

    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        Thread2 = New Thread(New ThreadStart(AddressOf SetText))
        Thread2.Start()
        Thread.Sleep(1000)
    End Sub

    Private Sub WriteTextSafe(text As String)
        If TextBox1.InvokeRequired Then
            Dim d As New SafeCallDelegate(AddressOf SetText)
            TextBox1.Invoke(d, New Object() {text})
        Else
            TextBox1.Text = text
        End If
    End Sub

    Private Sub SetText()
        WriteTextSafe("This text was set safely.")
    End Sub
End Class

Ejemplo: Usar un controlador de eventos BackgroundWorkerExample: Use a BackgroundWorker event handler

Una manera fácil de implementar el multithreading es con System.ComponentModel.BackgroundWorker el componente, que utiliza un modelo orientado a eventos.An easy way to implement multithreading is with the System.ComponentModel.BackgroundWorker component, which uses an event-driven model. El subproceso en segundo BackgroundWorker.DoWork plano ejecuta el evento, que no interactúa con el subproceso principal.The background thread runs the BackgroundWorker.DoWork event, which doesn't interact with the main thread. El subproceso principal ejecuta BackgroundWorker.ProgressChanged los BackgroundWorker.RunWorkerCompleted controladores de eventos y, que pueden llamar a los controles del subproceso principal.The main thread runs the BackgroundWorker.ProgressChanged and BackgroundWorker.RunWorkerCompleted event handlers, which can call the main thread's controls.

Para realizar una llamada BackgroundWorkersegura para subprocesos mediante, cree un método en el subproceso en segundo plano para realizar el trabajo y enlácelo DoWork al evento.To make a thread-safe call by using BackgroundWorker, create a method in the background thread to do the work, and bind it to the DoWork event. Cree otro método en el subproceso principal para informar de los resultados del trabajo en segundo plano y enlácelo al ProgressChanged evento RunWorkerCompleted o.Create another method in the main thread to report the results of the background work, and bind it to the ProgressChanged or RunWorkerCompleted event. Para iniciar el subproceso en segundo BackgroundWorker.RunWorkerAsyncplano, llame a.To start the background thread, call BackgroundWorker.RunWorkerAsync.

En el ejemplo se RunWorkerCompleted utiliza el controlador de eventos TextBox para establecer Text la propiedad del control.The example uses the RunWorkerCompleted event handler to set the TextBox control's Text property. Para obtener un ejemplo del ProgressChanged uso del evento BackgroundWorker, vea.For an example using the ProgressChanged event, see BackgroundWorker.

using System;
using System.ComponentModel;
using System.Drawing;
using System.Threading;
using System.Windows.Forms;

public class BackgroundWorkerForm : Form
{
    private BackgroundWorker backgroundWorker1;
    private Button button1;
    private TextBox textBox1;

    [STAThread]
    static void Main()
    {
        Application.SetCompatibleTextRenderingDefault(false);
        Application.EnableVisualStyles();
        Application.Run(new BackgroundWorkerForm());
    }
    public BackgroundWorkerForm()
    {
        backgroundWorker1 = new BackgroundWorker();
        backgroundWorker1.DoWork += new DoWorkEventHandler(BackgroundWorker1_DoWork);
        backgroundWorker1.RunWorkerCompleted += new RunWorkerCompletedEventHandler(BackgroundWorker1_RunWorkerCompleted);
        button1 = new Button
        {
            Location = new Point(15, 55),
            Size = new Size(240, 20),
            Text = "Set text safely with BackgroundWorker"
        };
        button1.Click += new EventHandler(Button1_Click);
        textBox1 = new TextBox
        {
            Location = new Point(15, 15),
            Size = new Size(240, 20)
        };
        Controls.Add(button1);
        Controls.Add(textBox1);
    }
    private void Button1_Click(object sender, EventArgs e)
    {
        backgroundWorker1.RunWorkerAsync();
    }

    private void BackgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
    {
        // Sleep 2 seconds to emulate getting data.
        Thread.Sleep(2000);
        e.Result = "This text was set safely by BackgroundWorker.";
    }

    private void BackgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        textBox1.Text = e.Result.ToString();
    }
}
Imports System.ComponentModel
Imports System.Drawing
Imports System.Threading
Imports System.Windows.Forms

Public Class BackgroundWorkerForm : Inherits Form

    Public Shared Sub Main()
        Application.SetCompatibleTextRenderingDefault(False)
        Application.EnableVisualStyles()
        Dim frm As New BackgroundWorkerForm()
        Application.Run(frm)
    End Sub

    Dim WithEvents BackgroundWorker1 As BackgroundWorker
    Dim WithEvents Button1 As Button
    Dim TextBox1 As TextBox

    Private Sub New()
        BackgroundWorker1 = New BackgroundWorker()
        Button1 = New Button()
        With Button1
            .Text = "Set text safely with BackgroundWorker"
            .Location = New Point(15, 55)
            .Size = New Size(240, 20)
        End With
        TextBox1 = New TextBox()
        With TextBox1
            .Location = New Point(15, 15)
            .Size = New Size(240, 20)
        End With
        Controls.Add(Button1)
        Controls.Add(TextBox1)
    End Sub

    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        BackgroundWorker1.RunWorkerAsync()
    End Sub

    Private Sub BackgroundWorker1_DoWork(sender As Object, e As DoWorkEventArgs) _
     Handles BackgroundWorker1.DoWork
        ' Sleep 2 seconds to emulate getting data.
        Thread.Sleep(2000)
        e.Result = "This text was set safely by BackgroundWorker."
    End Sub

    Private Sub BackgroundWorker1_RunWorkerCompleted(sender As Object, e As RunWorkerCompletedEventArgs) _
     Handles BackgroundWorker1.RunWorkerCompleted
        textBox1.Text = e.Result.ToString()
    End Sub
End Class

Vea tambiénSee also