La operación entre subprocesos no es válida: control al que se accede desde un subproceso que no es el subproceso en el que se creó

Tengo un escenario. (Windows Forms, C #, .NET)

  1. Hay un formulario principal que alberga cierto control de usuario.
  2. El control de usuario realiza una gran operación de datos, de modo que si llamo directamente al UserControl_Load método, la interfaz de usuario deja de responder durante 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 hecho su trabajo.
  5. Ahora vino un verdadero problema. Toda la interfaz de usuario (formulario principal y sus controles de usuario secundarios) se creó en el hilo principal principal. En el método LOAD del control de usuario, obtengo datos basados ​​en los valores de algún control (como el cuadro de texto) en userControl.

El pseudocódigo se verí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: control al que se accede desde un subproceso que no es el 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 wont 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 PERO PERO ... parece que he vuelto al punto de partida. La aplicación vuelve a dejar de responder. Parece deberse a la ejecución de la línea # 1 if condition. La tarea de carga la realiza nuevamente el hilo principal y no el tercero que generé.

No sé si lo percibí bien o mal. Soy nuevo en el enhebrado.

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

La situación es esta: Quiero cargar datos en una variable global basada en el valor de un control. No quiero cambiar el valor de un control del hilo secundario. No lo voy a hacer nunca desde un hilo secundario.

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

preguntado el 26 de septiembre de 08 a las 19:09

Para mi caso particular de este error, encontré que la solución es usar un BackgroundWorker en el formulario para manejar las partes del código con uso intensivo de datos. (es decir, ponga todo el código del problema en el método backgroundWorker1_DoWork () y llámelo a través de backgroundWorker1.RunWorkerAsync ()) ... Estas dos fuentes me señalaron en la dirección correcta: stackoverflow.com/questions/4806742/… youtube.com/watch?v=MLrrbG6V1zM -

22 Respuestas

Según Comentario de actualización de Prerak K (desde eliminado):

Supongo que no he presentado la pregunta correctamente.

La situación es la siguiente: quiero cargar datos en una variable global basada en el valor de un control. No quiero cambiar el valor de un control del hilo secundario. No lo voy a hacer nunca desde un hilo secundario.

Por lo tanto, solo acceda al valor para que los datos correspondientes se puedan obtener 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
    }
}

Haga su procesamiento serio en el hilo separado antes intenta 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;
        }
    }
}

Respondido el 20 de junio de 20 a las 10:06

Ha pasado un tiempo desde que hice programación en C #, pero según el artículo de MSDN y mi conocimiento irregular, lo parece. - jeff hubbard

La diferencia es que BeginInvoke () es asincrónico mientras que Invoke () se ejecuta de forma sincrónica. stackoverflow.com/questions/229554/… - frizsombor

Modelo de subprocesos en la interfaz de usuario

Por favor lea el Modelo de subprocesamiento en aplicaciones de interfaz de usuario (el antiguo enlace VB está aquí) para comprender los conceptos básicos. El vínculo 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 la interfaz de usuario), al que se le permite acceder Sistema.Windows.Forms.Control y sus subclases miembros.
  • Intento de acceder al miembro de Sistema.Windows.Forms.Control desde un subproceso diferente al 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:

enter image description here

enter image description here

Métodos BeginInvoke e Invoke

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

enter image description here

invocar

enter image description here

BeginInvoke

enter image description here

Solución de código

Leer las 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 es aquí.

Respondido el 12 de junio de 20 a las 09:06

Solo quieres usar Invoke or BeginInvoke por el mínimo trabajo requerido 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 usando Control.Invoke/Control.BeginInvoke solo para actualizar la interfaz de usuario. De esa manera, su hilo de interfaz de usuario será libre de manejar eventos de interfaz de usuario, etc.

Lee mi artículo de enhebrado para agendar una Ejemplo de WinForms - aunque el artículo fue escrito antes BackgroundWorker llegó a la escena, y me temo que no lo he actualizado en ese sentido. BackgroundWorker simplemente simplifica un poco la devolución de llamada.

Respondido 23 Abr '20, 09:04

aquí en esta condición mía. Ni siquiera estoy cambiando la interfaz de usuario. Solo estoy accediendo a sus valores actuales desde el hilo secundario. cualquier sugerencia de cómo implementar - prerak k

Aún necesita dirigirse al subproceso de la interfaz de usuario incluso solo para acceder a las propiedades. Si su método no puede continuar hasta que se accede al valor, puede usar un delegado que devuelva el valor. Pero sí, vaya a través del hilo de la interfaz de usuario. - jon skeet

Hola Jon, creo que me estás dirigiendo en la dirección correcta. Sí, necesito el valor sin él, no puedo continuar. Por favor, ¿podría hablar sobre eso? Usar un delegado que devuelva un valor. Gracias - prerak k

Utilice un delegado como Func : string text = textbox1.Invoke ((Func ) () => cuadro de texto1.Texto); (Eso es asumiendo que está usando C # 3.0; de lo contrario, podría usar un método anónimo). jon skeet

Sé que ahora es demasiado tarde. Sin embargo, incluso hoy, ¿tiene problemas para acceder a los controles de subprocesos cruzados? Esta es la respuesta más corta hasta la fecha: P

Invoke(new Action(() =>
                {
                    label1.Text = "WooHoo!!!";
                }));

Así es como accedo a cualquier control de formulario desde un hilo.

Respondido 17 Oct 17, 12:10

Esto me da Invoke or BeginInvoke cannot be called on a control until the window handle has been created. Lo resolví aquí - rupweb

He tenido este problema con el FileSystemWatcher y descubrió que el siguiente código resolvió el problema:

fsw.SynchronizingObject = this

A continuación, el control utiliza el objeto de formulario actual para tratar los eventos y, por lo tanto, estará en el mismo hilo.

respondido 30 mar '16, 18:03

Esto salvó mi tocino. En VB.NET utilicé .SynchronizingObject = Me - codificacion

Encuentro que el código de verificación e invocación que debe incluirse en todos los métodos relacionados con los formularios es demasiado detallado e innecesario. Aquí hay un método de extensión simple que le permite eliminarlo por completo:

public static class Extensions
{
    public static void Invoke<TControlType>(this TControlType control, Action<TControlType> del) 
        where TControlType : Control
        {
            if (control.InvokeRequired)
                control.Invoke(new Action(() => del(control)));
            else
                del(control);
    }
}

Y luego puedes simplemente hacer esto:

textbox1.Invoke(t => t.Text = "A");

No más tonterías, simple.

Respondido 15 ago 16, 14:08

lo que está ahí - Rawat

@Rawat t en este caso será textbox1 - se pasa como argumento - Rob ♦

Los controles en .NET generalmente no son seguros para subprocesos. Eso significa que no debe acceder a un control desde un hilo que no sea aquel en el que vive. Para evitar esto, necesitas invocar el control, que es lo que está intentando su segunda muestra.

Sin embargo, en su caso, todo lo que ha hecho es pasar el método de larga duración al hilo principal. Por supuesto, eso no es realmente lo que quieres hacer. Necesita repensar esto un poco para que todo lo que esté haciendo en el hilo principal sea establecer una propiedad rápida aquí y allá.

Respondido el 11 de diciembre de 09 a las 00:12

La solución más limpia (y adecuada) para los problemas de subprocesos cruzados de la interfaz de usuario es usar SynchronizationContext, consulte Sincronizar llamadas a la interfaz de usuario en una aplicación multiproceso artículo, lo explica muy bien.

Respondido 27 Abr '09, 06:04

Una nueva apariencia usando Async / Await y devoluciones de llamada. Solo necesita una línea de código si mantiene el método de extensión en su proyecto.

/// <summary>
/// A new way to use Tasks for Asynchronous calls
/// </summary>
public class Example
{
    /// <summary>
    /// No more delegates, background workers etc. just one line of code as shown below
    /// Note it is dependent on the XTask class shown next.
    /// </summary>
    public async void ExampleMethod()
    {
        //Still on GUI/Original Thread here
        //Do your updates before the next line of code
        await XTask.RunAsync(() =>
        {
            //Running an asynchronous task here
            //Cannot update GUI Thread here, but can do lots of work
        });
        //Can update GUI/Original thread on this line
    }
}

/// <summary>
/// A class containing extension methods for the Task class 
/// Put this file in folder named Extensions
/// Use prefix of X for the class it Extends
/// </summary>
public static class XTask
{
    /// <summary>
    /// RunAsync is an extension method that encapsulates the Task.Run using a callback
    /// </summary>
    /// <param name="Code">The caller is called back on the new Task (on a different thread)</param>
    /// <returns></returns>
    public async static Task RunAsync(Action Code)
    {
        await Task.Run(() =>
        {
            Code();
        });
        return;
    }
}

Puede agregar otras cosas al método de Extensión, como envolverlo en una declaración Try / Catch, permitiendo que la persona que llama le diga qué tipo debe regresar después de la finalización, una devolución de llamada de excepción para la persona que llama:

Adición de Try Catch, Registro automático de excepciones y CallBack

    /// <summary>
    /// Run Async
    /// </summary>
    /// <typeparam name="T">The type to return</typeparam>
    /// <param name="Code">The callback to the code</param>
    /// <param name="Error">The handled and logged exception if one occurs</param>
    /// <returns>The type expected as a competed task</returns>

    public async static Task<T> RunAsync<T>(Func<string,T> Code, Action<Exception> Error)
    {
       var done =  await Task<T>.Run(() =>
        {
            T result = default(T);
            try
            {
               result = Code("Code Here");
            }
            catch (Exception ex)
            {
                Console.WriteLine("Unhandled Exception: " + ex.Message);
                Console.WriteLine(ex.StackTrace);
                Error(ex);
            }
            return result;

        });
        return done;
    }
    public async void HowToUse()
    {
       //We now inject the type we want the async routine to return!
       var result =  await RunAsync<bool>((code) => {
           //write code here, all exceptions are logged via the wrapped try catch.
           //return what is needed
           return someBoolValue;
       }, 
       error => {

          //exceptions are already handled but are sent back here for further processing
       });
        if (result)
        {
            //we can now process the result because the code above awaited for the completion before
            //moving to this statement
        }
    }

Respondido 04 Feb 17, 16:02

Esta no es la forma recomendada de resolver este error, pero puede eliminarlo rápidamente, hará el trabajo. Prefiero esto para prototipos o demostraciones. agregar

CheckForIllegalCrossThreadCalls = false

in Form1() constructor.

Respondido el 03 de Septiembre de 17 a las 21:09

Siga la forma más simple (en mi opinión) de modificar objetos de otro hilo:

using System.Threading.Tasks;
using System.Threading;

namespace TESTE
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            Action<string> DelegateTeste_ModifyText = THREAD_MOD;
            Invoke(DelegateTeste_ModifyText, "MODIFY BY THREAD");
        }

        private void THREAD_MOD(string teste)
        {
            textBox1.Text = teste;
        }
    }
}

contestado el 06 de mayo de 16 a las 17:05

Debe mirar el ejemplo de Backgroundworker:
http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx Especialmente cómo interactúa con la capa de la interfaz de usuario. Según su publicación, esto parece responder a sus problemas.

Respondido el 26 de Septiembre de 08 a las 22:09

Encontré una necesidad para esto mientras programaba un controlador de aplicación monotouch para iOS-Phone en un proyecto prototipo de winforms de visual studio fuera de xamarin stuidio. Preferiendo programar en VS sobre xamarin studio tanto como sea posible, quería que el controlador estuviera completamente desacoplado del marco del teléfono. De esta manera, implementar esto para otros marcos como Android y Windows Phone sería mucho más fácil para usos futuros.

Quería una solución en la que la GUI pudiera responder a eventos sin la carga de lidiar con el código de conmutación de subprocesos cruzados detrás de cada clic de botón. Básicamente, deje que el controlador de clase maneje eso para mantener simple el código del cliente. Posiblemente podría tener muchos eventos en la GUI donde, como si pudiera manejarlo en un lugar en la clase, sería más limpio. No soy un experto en múltiples temas, avíseme si esto es defectuoso.

public partial class Form1 : Form
{
    private ExampleController.MyController controller;

    public Form1()
    {          
        InitializeComponent();
        controller = new ExampleController.MyController((ISynchronizeInvoke) this);
        controller.Finished += controller_Finished;
    }

    void controller_Finished(string returnValue)
    {
        label1.Text = returnValue; 
    }

    private void button1_Click(object sender, EventArgs e)
    {
        controller.SubmitTask("Do It");
    }
}

El formulario de la GUI no sabe que el controlador está ejecutando tareas asincrónicas.

public delegate void FinishedTasksHandler(string returnValue);

public class MyController
{
    private ISynchronizeInvoke _syn; 
    public MyController(ISynchronizeInvoke syn) {  _syn = syn; } 
    public event FinishedTasksHandler Finished; 

    public void SubmitTask(string someValue)
    {
        System.Threading.ThreadPool.QueueUserWorkItem(state => submitTask(someValue));
    }

    private void submitTask(string someValue)
    {
        someValue = someValue + " " + DateTime.Now.ToString();
        System.Threading.Thread.Sleep(5000);
//Finished(someValue); This causes cross threading error if called like this.

        if (Finished != null)
        {
            if (_syn.InvokeRequired)
            {
                _syn.Invoke(Finished, new object[] { someValue });
            }
            else
            {
                Finished(someValue);
            }
        }
    }
}

Respondido el 16 de diciembre de 15 a las 21:12

Aquí hay una forma alternativa si el objeto con el que está trabajando no tiene

(InvokeRequired)

Esto es útil si está trabajando con el formulario principal en una clase que no sea el formulario principal con un objeto que está en el formulario principal, pero no tiene InvokeRequired

delegate void updateMainFormObject(FormObjectType objectWithoutInvoke, string text);

private void updateFormObjectType(FormObjectType objectWithoutInvoke, string text)
{
    MainForm.Invoke(new updateMainFormObject(UpdateObject), objectWithoutInvoke, text);
}

public void UpdateObject(ToolStripStatusLabel objectWithoutInvoke, string text)
{
    objectWithoutInvoke.Text = text;
}

Funciona igual que el anterior, pero es un enfoque diferente si no tiene un objeto con invokerequired, pero tiene acceso al MainForm

contestado el 24 de mayo de 12 a las 23:05

En la misma línea que las respuestas anteriores, pero una adición muy corta que permite usar todas las propiedades de Control sin tener una excepción de invocación de subprocesos cruzados.

Método de ayuda

/// <summary>
/// Helper method to determin if invoke required, if so will rerun method on correct thread.
/// if not do nothing.
/// </summary>
/// <param name="c">Control that might require invoking</param>
/// <param name="a">action to preform on control thread if so.</param>
/// <returns>true if invoke required</returns>
public bool ControlInvokeRequired(Control c, Action a)
{
    if (c.InvokeRequired) c.Invoke(new MethodInvoker(delegate
    {
        a();
    }));
    else return false;

    return true;
}

Uso de la muestra

// usage on textbox
public void UpdateTextBox1(String text)
{
    //Check if invoke requied if so return - as i will be recalled in correct thread
    if (ControlInvokeRequired(textBox1, () => UpdateTextBox1(text))) return;
    textBox1.Text = ellapsed;
}

//Or any control
public void UpdateControl(Color c, String s)
{
    //Check if invoke requied if so return - as i will be recalled in correct thread
    if (ControlInvokeRequired(myControl, () => UpdateControl(c, s))) return;
    myControl.Text = s;
    myControl.BackColor = c;
}

Respondido el 16 de diciembre de 15 a las 19:12

this.Invoke(new MethodInvoker(delegate
            {
                //your code here;
            }));

respondido 02 nov., 17:22

Por ejemplo, para obtener el texto de un control del hilo de la interfaz de usuario:

Private Delegate Function GetControlTextInvoker(ByVal ctl As Control) As String

Private Function GetControlText(ByVal ctl As Control) As String
    Dim text As String

    If ctl.InvokeRequired Then
        text = CStr(ctl.Invoke(
            New GetControlTextInvoker(AddressOf GetControlText), ctl))
    Else
        text = ctl.Text
    End If

    Return text
End Function

respondido 22 nov., 18:03

Misma pregunta: cómo-actualizar-la-interfaz-gráfica-de-otro-hilo-en-c

Dos caminos:

  1. Devuelve el valor en e.result y úsalo para establecer el valor del cuadro de texto en el evento backgroundWorker_RunWorkerCompleted

  2. Declare alguna variable para contener este tipo de valores en una clase separada (que funcionará como contenedor de datos). Cree una instancia estática de esta clase y puede acceder a ella a través de cualquier hilo.

Ejemplo:

public  class data_holder_for_controls
{
    //it will hold value for your label
    public  string status = string.Empty;
}

class Demo
{
    public static  data_holder_for_controls d1 = new data_holder_for_controls();
    static void Main(string[] args)
    {
        ThreadStart ts = new ThreadStart(perform_logic);
        Thread t1 = new Thread(ts);
        t1.Start();
        t1.Join();
        //your_label.Text=d1.status; --- can access it from any thread 
    }

    public static void perform_logic()
    {
        //put some code here in this function
        for (int i = 0; i < 10; i++)
        {
            //statements here
        }
        //set result in status variable
        d1.status = "Task done";
    }
}

Respondido 01 Oct 17, 09:10

Simplemente usa esto:

this.Invoke((MethodInvoker)delegate
            {
                YourControl.Property= value; // runs thread safe
            });

Respondido 28 Abr '19, 21:04

Una forma sencilla y reutilizable de solucionar este problema.

Método de extensión

public static class FormExts
{
    public static void LoadOnUI(this Form frm, Action action)
    {
        if (frm.InvokeRequired) frm.Invoke(action);
        else action.Invoke();
    }
}

Uso de la muestra

private void OnAnyEvent(object sender, EventArgs args)
{
    this.LoadOnUI(() =>
    {
        label1.Text = "";
        button1.Text = "";
    });
}

contestado el 08 de mayo de 20 a las 19:05

¿Alguien puede argumentar en contra de este enfoque? Parece increíblemente fácil en comparación con las respuestas populares. - Programador Paul

Acción y; // declarado dentro de la clase

label1.Invoke (y = () => label1.Text = "texto");

Respondido el 17 de diciembre de 17 a las 06:12

Hay dos opciones para las operaciones de subprocesos cruzados.

Control.InvokeRequired Property 

y el segundo es usar

SynchronizationContext Post Method

Control.InvokeRequired solo es útil cuando los controles de trabajo heredados de la clase Control mientras SynchronizationContext se pueden usar en cualquier lugar. Alguna información útil se encuentra en los siguientes enlaces

Interfaz de usuario de actualización de subprocesos cruzados | .Neto

Interfaz de usuario de actualización de subprocesos cruzados mediante SynchronizationContext | .Neto

Respondido 03 Abr '18, 10:04

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.