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

StackOverflow https://stackoverflow.com/questions/142003

Pregunta

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

  1. Hay un formulario principal que alberga algún control de usuario.
  2. El control de usuario realiza algunas operaciones de datos pesados, de modo que si llamo directamente al método UserControl_Load , la IU dejará de responder durante el tiempo de ejecución del método de carga.
  3. Para superar esto, cargo datos en diferentes hilos (tratando de cambiar el código existente tan poco como pueda)
  4. Utilicé un subproceso 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 problema real. Toda la interfaz de usuario (formulario principal y sus controles de usuario secundarios) se creó en el hilo principal primario. En el método LOAD del control de usuario, estoy obteniendo datos basados ??en los valores de algún control (como 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 de subprocesos no válida: control al que se accede desde un subproceso que no sea el subproceso en el que se creó.

Para saber más sobre esto hice un poco de 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 estoy de vuelta al punto uno. La aplicación de nuevo dejar de responder Parece que se debe a la ejecución de la línea # 1 si la condición. La tarea de carga la realiza nuevamente el subproceso principal y no el tercero que generé.

No sé si percibí esto 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 la siguiente : quiero cargar datos en una variable global en función del valor de un control. No quiero cambiar el valor de un control desde el subproceso secundario. No lo voy a hacer nunca desde un hilo infantil.

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

¿Fue útil?

Solución

Según Comentario de actualización de Prerak K (desde que se eliminó):

  

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 desde el subproceso secundario. No lo voy a hacer nunca desde un hilo secundario.

     

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

La solución que desea debería tener el siguiente aspecto:

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 subproceso separado antes que 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;
        }
    }
}

Otros consejos

Modelo de subprocesamiento en la interfaz de usuario

Lea el Modelo de subprocesamiento en aplicaciones UI para entender 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 la interfaz de usuario), que puede acceder a System.Windows.Forms.Control y sus subclases miembros.
  • Intente acceder al miembro de System.Windows.Forms .Control de un subproceso diferente al subproceso de la interfaz de usuario causará una excepción de subproceso.
  • Dado que solo hay un hilo, todas las operaciones de IU se ponen en cola como elementos de trabajo en ese hilo:

ingrese la descripción de la imagen aquí

ingrese la descripción de la imagen aquí

BeginInvoke e invocar métodos

ingrese la descripción de la imagen aquí

Invocar

ingrese la descripción de la imagen aquí

BeginInvoke

ingrese la descripción de la imagen aquí

Solución de código

Lea las respuestas en la pregunta Cómo actualizar la GUI de otro hilo en C #? . Para C # 5.0 y .NET 4.5, la solución recomendada es aquí .

Solo desea utilizar Invoke o BeginInvoke para el trabajo mínimo requerido para cambiar la interfaz de usuario. Tu " pesado " El método debe ejecutarse en otro hilo (por ejemplo, a través de BackgroundWorker) pero luego usar Control.Invoke / Control.BeginInvoke solo para actualizar la interfaz de usuario. De esa manera, su hilo de UI será libre de manejar eventos de IU, etc.

Consulte mi artículo sobre subprocesos para obtener un ejemplo de WinForms - aunque el artículo fue escrito antes de que BackgroundWorker llegara a la escena, y me temo que no lo he actualizado. que respeto BackgroundWorker simplemente simplifica un poco la devolución de llamada.

He tenido este problema con FileSystemWatcher y encontré que el siguiente código solucionó el problema:

fsw.SynchronizingObject = this

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

Sé que es demasiado tarde ahora. Sin embargo, incluso hoy en día si tiene problemas para acceder a los controles de múltiples hilos? 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.

Los controles en .NET generalmente no son seguros para subprocesos. Eso significa que no debe acceder a un control desde un subproceso que no sea el que vive. Para solucionar esto, debe 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. Debe repensar esto un poco para que todo lo que esté haciendo en el hilo principal sea establecer una propiedad rápida aquí y allá.

Considero que el código de verificación e invocación que debe estar lleno de todos los métodos relacionados con los formularios es demasiado detallado e innecesario. Aquí hay un método de extensión simple que te 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 perder el tiempo - simple.

La solución más limpia (y adecuada) para los problemas de subprocesos de UI es utilizar SynchronizationContext, consulte Sincronizando llamadas a la UI en una aplicación de múltiples hilos artículo, lo explica muy bien.

Una nueva apariencia con 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 devolver después de completarse, una devolución de llamada de excepción para la persona que llama:

Adición de Try Catch, Auto Exception Logging and 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
        }
    }

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

Siga la forma más sencilla (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;
        }
    }
}

Encontré una necesidad de esto mientras programaba un controlador de aplicación monotouch de iOS-Phone en un proyecto visual de prototipos de proyectos de prototipos fuera del estuidio de xamarin. Prefiriendo programar en VS sobre xamarin studio lo más posible, quería que el controlador estuviera completamente desacoplado del marco del teléfono. De esta manera, implementar esto en 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 los eventos sin la carga de lidiar con el código de conmutación de subprocesos tras cada clic de botón. Básicamente, deje que el controlador de clase maneje eso para mantener el código del cliente simple. Posiblemente podría tener muchos eventos en la GUI donde, como si pudiera manejarlo en un lugar de la clase, estaría más limpio. No soy un experto en múltiples adiestramientos, déjame saber 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 asíncronas.

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);
            }
        }
    }
}

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

CheckForIllegalCrossThreadCalls = false

en Form1 () constructor.

Esta es 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 distinta del 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 de la misma manera que la anterior, pero es un enfoque diferente si no tiene un objeto que se requiere, pero tiene acceso a MainForm

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

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 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;
}
this.Invoke(new MethodInvoker(delegate
            {
                //your code here;
            }));

Por ejemplo, para obtener el texto de un control del subproceso 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

La misma pregunta: cómo hacerlo para actualizar the-gui-from-another-thread-in-c

Dos maneras:

  1. Devuelva el valor en e.result y utilícelo para establecer su valor de cuadro de texto en el evento backgroundWorker_RunWorkerCompleted

  2. Declare una variable para mantener este tipo de valores en una clase separada (que funcionará como titular 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";
    }
}

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

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

Simplemente usa esto:

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

Hay dos opciones para las operaciones de subprocesos.

Control.InvokeRequired Property 

y el segundo es usar

SynchronizationContext Post Method

Control.InvokeRequired solo es útil cuando se trabaja con controles heredados de la clase Control mientras que SynchronizationContext se puede usar en cualquier lugar. Algunos datos útiles son los siguientes enlaces

UI de actualización de subprocesos cruzados | .Net

Interfaz de usuario de actualización de subprocesos mediante SynchronizationContext | .Net

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top