Operación entre subprocesos no válida: se accede al control desde un subproceso distinto del subproceso en el que se creó

Resuelto Prerak K asked hace 15 años • 22 respuestas

Tengo un escenario. (Formularios de Windows, C#, .NET)

  1. Hay un formulario principal que alberga cierto control del usuario.
  2. El control de usuario realiza algunas operaciones con datos pesados, de modo que si llamo directamente al UserControl_Loadmétodo, la interfaz de usuario deja de responder mientras dura la ejecución del método de carga.
  3. Para superar esto, cargo datos en diferentes hilos (tratando de cambiar el código existente lo menos que puedo)
  4. Utilicé un hilo de trabajo en segundo plano que cargará los datos y, cuando termine, notificará a la aplicación que ha realizado su trabajo.
  5. Ahora surgió un verdadero problema. Toda la interfaz de usuario (el formulario principal y sus controles de usuario secundarios) se creó en el hilo principal principal. En el método LOAD del control de usuario, estoy obteniendo datos basados ​​en los valores de algún control (como el cuadro de texto) en el control de usuario.

El pseudocódigo quedaría así:

CÓDIGO 1

UserContrl1_LoadDataMethod()
{
    if (textbox1.text == "MyName") // This gives exception
    {
        //Load data corresponding to "MyName".
        //Populate a globale variable List<string> which will be binded to grid at some later stage.
    }
}

La excepción que dio fue

Operación entre subprocesos no válida: se accede al control desde un subproceso distinto del subproceso en el que se creó.

Para saber más sobre esto busqué en Google y surgió una sugerencia como usar el siguiente código

CÓDIGO 2

UserContrl1_LoadDataMethod()
{
    if (InvokeRequired) // Line #1
    {
        this.Invoke(new MethodInvoker(UserContrl1_LoadDataMethod));
        return;
    }

    if (textbox1.text == "MyName") // Now it won't give an exception
    {
    //Load data correspondin to "MyName"
        //Populate a globale variable List<string> which will be binded to grid at some later stage
    }
}

Pero todavía parece que he vuelto al punto de partida. La aplicación nuevamente deja de responder. Parece deberse a la ejecución de la línea #1 si condición. La tarea de carga la realiza nuevamente el hilo principal y no el tercero que generé.

No sé si percibí esto bien o mal.

¿Cómo resuelvo esto y también cuál es el efecto de la ejecución de la Línea n.º 1 si se bloquea?

La situación es la siguiente : quiero cargar datos en una variable global según el valor de un control. No quiero cambiar el valor de un control del hilo secundario. No lo haré nunca desde un hilo infantil.

Por lo tanto, solo se accede al valor para que los datos correspondientes se puedan recuperar de la base de datos.

Prerak K avatar Sep 27 '08 04:09 Prerak K
Aceptado

Según el comentario de actualización de Prerak K (ya eliminado):

Supongo que no he presentado la pregunta correctamente.

La situación es la siguiente: quiero cargar datos en una variable global según el valor de un control. No quiero cambiar el valor de un control del hilo secundario. No lo haré nunca desde un hilo infantil.

Por lo tanto, solo se accede al valor para poder obtener los datos correspondientes de la base de datos.

La solución que desea entonces debería verse así:

UserContrl1_LOadDataMethod()
{
    string name = "";
    if(textbox1.InvokeRequired)
    {
        textbox1.Invoke(new MethodInvoker(delegate { name = textbox1.text; }));
    }
    if(name == "MyName")
    {
        // do whatever
    }
}

Realice su procesamiento serio en el hilo separado antes de intentar volver al hilo del control. Por ejemplo:

UserContrl1_LOadDataMethod()
{
    if(textbox1.text=="MyName") //<<======Now it wont give exception**
    {
        //Load data correspondin to "MyName"
        //Populate a globale variable List<string> which will be
        //bound to grid at some later stage
        if(InvokeRequired)
        {
            // after we've done all the processing, 
            this.Invoke(new MethodInvoker(delegate {
                // load the control with the appropriate data
            }));
            return;
        }
    }
}
Jeff Hubbard avatar Sep 26 '2008 21:09 Jeff Hubbard

Modelo de subprocesos en la interfaz de usuario

Lea el modelo de subprocesos en aplicaciones de interfaz de usuario ( el antiguo enlace de VB está aquí ) para comprender los conceptos básicos. El enlace navega a la página que describe el modelo de subprocesos de WPF. Sin embargo, Windows Forms utiliza la misma idea.

El hilo de la interfaz de usuario

  • Solo hay un subproceso (subproceso de interfaz de usuario) al que se le permite acceder a System.Windows.Forms.Control y sus miembros de subclases.
  • El intento de acceder al miembro de System.Windows.Forms.Control desde un subproceso diferente al subproceso de la interfaz de usuario provocará una excepción entre subprocesos.
  • Dado que solo hay un subproceso, todas las operaciones de la interfaz de usuario se ponen en cola como elementos de trabajo en ese subproceso:

ingrese la descripción de la imagen aquí

  • Si no hay trabajo para el subproceso de la interfaz de usuario, entonces hay espacios inactivos que pueden ser utilizados por una informática no relacionada con la interfaz de usuario.
  • Para utilizar los espacios mencionados, utilice los métodos System.Windows.Forms.Control.Invoke o System.Windows.Forms.Control.BeginInvoke :

ingrese la descripción de la imagen aquí

Métodos BeginInvoke e Invoke

  • La sobrecarga informática del método que se invoca debe ser pequeña, al igual que la sobrecarga informática de los métodos del controlador de eventos porque allí se utiliza el hilo de la interfaz de usuario, el mismo que es responsable de manejar la entrada del usuario. Independientemente de si se trata de System.Windows.Forms.Control.Invoke o System.Windows.Forms.Control.BeginInvoke .
  • Para realizar operaciones informáticas costosas, utilice siempre un hilo separado. Desde .NET 2.0, BackgroundWorker se dedica a realizar operaciones informáticas costosas en Windows Forms. Sin embargo, en nuevas soluciones debe utilizar el patrón async-await como se describe aquí .
  • Utilice los métodos System.Windows.Forms.Control.Invoke o System.Windows.Forms.Control.BeginInvoke solo para actualizar una interfaz de usuario. Si los usa para cálculos pesados, su aplicación bloqueará:

ingrese la descripción de la imagen aquí

Invocar

  • System.Windows.Forms.Control.Invoke hace que un subproceso independiente espere hasta que se complete el método invocado:

ingrese la descripción de la imagen aquí

Comenzar invocar

  • System.Windows.Forms.Control.BeginInvoke no hace que el hilo separado espere hasta que se complete el método invocado:

ingrese la descripción de la imagen aquí

Solución de código

Leer respuestas a la pregunta ¿Cómo actualizar la GUI desde otro hilo en C#? . Para C# 5.0 y .NET 4.5, la solución recomendada está aquí .

Ryszard Dżegan avatar Sep 26 '2013 08:09 Ryszard Dżegan

Solo desea utilizar Invokeo BeginInvokepara el trabajo mínimo necesario para cambiar la interfaz de usuario. Su método "pesado" debería ejecutarse en otro hilo (por ejemplo, a través de BackgroundWorker) pero luego usar Control.Invoke/ Control.BeginInvokesolo para actualizar la interfaz de usuario. De esa manera, su hilo de interfaz de usuario podrá manejar libremente eventos de interfaz de usuario, etc.

Consulte mi artículo sobre subprocesos para ver un ejemplo de WinForms , aunque el artículo se escribió antes de que BackgroundWorkerapareciera en escena y me temo que no lo he actualizado en ese sentido. BackgroundWorkersimplemente simplifica un poco la devolución de llamada.

Jon Skeet avatar Sep 26 '2008 21:09 Jon Skeet