c# - una - system windows forms control invoke




Operación de subprocesos no válida: control accedido desde un subproceso distinto del subproceso en el que se creó (14)

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

  1. Hay una forma principal que alberga algún control de usuario.
  2. El control de usuario realiza algunas operaciones de datos pesados, de manera que si yo llamo directamente al método UserControl_Load , la IU no responde durante el tiempo que 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 tan poco como pueda)
  4. Utilicé un subproceso de trabajo en segundo plano que cargará los datos y, cuando haya terminado, notificará a la aplicación que ha realizado 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 distinto del 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 cuadrado uno. La aplicación vuelve a ser insensible. 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 enhebrar.

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

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 infantil.

Solo acceda al valor para que los datos correspondientes puedan ser recuperados de la base de datos.


Modelo de roscado en la interfaz de usuario

Lea el Modelo de subprocesos en las aplicaciones de UI para comprender 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.
  • 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 de subproceso.
  • Dado que solo hay un hilo, todas las operaciones de IU se ponen en cola como elementos de trabajo en ese hilo:

Métodos BeginInvoke e Invoke

  • La sobrecarga de cómputo del método invocado debe ser pequeña, así como la sobrecarga de cómputo de los métodos de manejo 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 de si se trata de System.Windows.Forms.Control.Invoke o System.Windows.Forms.Control.BeginInvoke .
  • Para realizar operaciones costosas de computación siempre use hilos separados. Desde .NET 2.0 BackgroundWorker se dedica a realizar operaciones costosas de computación en Windows Forms. Sin embargo, en las nuevas soluciones, debe usar el patrón async-await como se describe here .
  • Use 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á:

Invocar

Comenzarinvocarse

Solución de código

Lea las respuestas en 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á here .


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

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


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.


Descubrí la necesidad de esto mientras programaba un controlador de aplicación monotouch de iOS-Phone en un proyecto de prototipo de estudio visual, fuera del stuidio de xamarin. Prefiriendo 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 en otros marcos como Android y Windows Phone sería mucho más fácil para usos futuros.

Quería una solución donde 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 GUI no tiene conocimiento de 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.




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. 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á.


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

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.


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

Solo desea usar Invoke o BeginInvoke para el trabajo mínimo requerido para cambiar la interfaz de usuario. Su método "pesado" debe ejecutarse en otro subproceso (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.

Vea mi artículo de subprocesos para ver un ejemplo de WinForms , aunque el artículo se escribió antes de que BackgroundWorker llegara a la escena, y me temo que no lo he actualizado al respecto. BackgroundWorker simplemente simplifica un poco la devolución de llamada.


La misma pregunta: cómo-actualizar-la-gui-de-otro-hilo-en-c

Dos caminos:

  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 alguna 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";
    }
}

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




invoke