Operación entre hilos no válida: control al que se accede desde un hilo que no sea el hilo en el que se creó

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

  1. Hay un formulario principal que alberga algún control del usuario.
  2. El control de usuario realiza una operación de datos pesados, de modo que si llamo directamente al método UserControl_Load , la UI deja 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 lo menos posible)
  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 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 usercontrol estoy obteniendo datos basados ​​en los valores de algún control (como el cuadro de texto) en userControl.

El pseudocódigo sería así:

CÓDIGO 1

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

La excepción que dio fue

Operación entre hilos no válida: control al que se accede desde un hilo que no sea el hilo en el que se creó.

Para saber más sobre esto, busqué en Google y me 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 which will be binded to grid at some later stage } } 

PERO PERO PERO … parece que he vuelto al punto de partida. La aplicación nuevamente deja de responder. Parece ser debido a la ejecución de la línea # 1 si la condición. La tarea de carga nuevamente se realiza mediante el hilo padre y no el tercero que engendré.

No sé si percibí esto bien o mal. Soy nuevo en el roscado.

¿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 basada en el valor de un control. No quiero cambiar el valor de un control desde el hilo hijo. No voy a hacerlo nunca desde un hilo hijo.

Entonces solo acceda al valor para que los datos correspondientes puedan ser extraídos de la base de datos.

Según el 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 en función del valor de un control. No quiero cambiar el valor de un control desde el hilo hijo. No voy a hacerlo nunca desde un hilo hijo.

Entonces, solo acceda al valor para que los datos correspondientes puedan obtenerse 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 por 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 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; } } } 

Enhebrado del modelo en UI

Lea el Modelo de subprocesamiento en las aplicaciones de interfaz de usuario para comprender los conceptos básicos. El enlace navega a la página que describe el modelo de subprocesamiento de WPF. Sin embargo, Windows Forms utiliza la misma idea.

El hilo de UI

  • Solo hay un hilo (subproceso de interfaz de usuario), que tiene permiso para acceder a System.Windows.Forms.Control y sus miembros de subclases.
  • Intentar acceder al miembro de System.Windows.Forms.Control desde un subproceso diferente del subproceso de la interfaz de usuario provocará una excepción entre subprocesos.
  • Como solo hay un hilo, todas las operaciones de IU se ponen en cola como elementos de trabajo en ese hilo:

enter image description here

  • Si no hay trabajo para el hilo de la interfaz de usuario, existen espacios vacíos que pueden ser utilizados por una informática no relacionada con la interfaz de usuario.
  • Para utilizar las lagunas mencionadas use los métodos System.Windows.Forms.Control.Invoke o System.Windows.Forms.Control.BeginInvoke :

enter image description here

Métodos BeginInvoke e Invoke

  • La sobrecarga informática del método invocado 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 se encarga de manejar la entrada del usuario. Independientemente de si esto es System.Windows.Forms.Control.Invoke o System.Windows.Forms.Control.BeginInvoke .
  • Para realizar operaciones costosas de computación, siempre use hilo separado. Desde .NET 2.0 BackgroundWorker está dedicado 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 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á:

enter image description here

Invocar

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

enter image description here

BeginInvoke

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

enter image description here

Solución de código

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

Solo desea usar Invoke o BeginInvoke para el mínimo trabajo requerido para cambiar la UI. Su método “pesado” debería ejecutarse en otro hilo (por ejemplo, a través de BackgroundWorker) pero luego usar Control.Invoke / Control.BeginInvoke solo para actualizar la IU. De esta forma, su subproceso de interfaz de usuario será libre para manejar eventos de interfaz de usuario, etc.

Ver mi artículo de subprocesos para 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 al respecto. BackgroundWorker simplemente simplifica un poco la callback.

Tuve este problema con FileSystemWatcher y descubrí que el siguiente código resolvía el problema:

fsw.SynchronizingObject = this

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

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

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

Encuentro que el código de verificación e invocación que debe estar cubierto de todos los métodos relacionados con los formularios es demasiado prolijo 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(this TControlType control, Action 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 líos, simple.

Sé que es demasiado tarde ahora. ¿Sin embargo, incluso hoy si tiene problemas para acceder a los controles de cruce de 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.

La solución más limpia (y adecuada) para problemas de cross-threading de UI es usar SynchronizationContext, ver Sincronización de llamadas a la UI en un artículo de aplicación multihilo , lo explica muy bien.

Sigue 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 DelegateTeste_ModifyText = THREAD_MOD; Invoke(DelegateTeste_ModifyText, "MODIFY BY THREAD"); } private void THREAD_MOD(string teste) { textBox1.Text = teste; } } } 

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

 ///  /// A new way to use Tasks for Asynchronous calls ///  public class Example { ///  /// No more delegates, background workers etc. just one line of code as shown below /// Note it is dependent on the XTask class shown next. ///  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 } } ///  /// 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 ///  public static class XTask { ///  /// RunAsync is an extension method that encapsulates the Task.Run using a callback ///  /// The caller is called back on the new Task (on a different thread) ///  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 statement Try / Catch, lo que permite que la persona que llama le diga qué tipo devolver después de la finalización, una callback de excepción a la persona que llama:

Agregar Try Catch, Auto Exception Logging y CallBack

  ///  /// Run Async ///  /// The type to return /// The callback to the code /// The handled and logged exception if one occurs /// The type expected as a competed task public async static Task RunAsync(Func Code, Action Error) { var done = await Task.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((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 de la interfaz de usuario. Según su publicación, esto parece responder a sus problemas.

Encontré una necesidad para esto mientras progtwigba un controlador de aplicación monotouch de iOS-Phone en un proyecto de prototipo de winforms de estudio visual fuera de xamarin stuidio. Prefiriendo progtwigr tanto en VS como en xamarin studio, quería que el controlador se desacople por completo de la estructura del teléfono. De esta forma, 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 los eventos sin la carga de lidiar con el código de cambio de cruce cruzado detrás de 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 podría manejarlos en un lugar de la clase más limpio. No soy un experto en multitarea, 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"); } } 

La forma de GUI no sabe que el controlador ejecuta 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); } } } } 

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

 CheckForIllegalCrossThreadCalls = false 

en el constructor Form1() .

Aquí hay una manera 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 InvokeRequirido

 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 a MainForm

Por ejemplo, para obtener el texto de un control de la secuencia de UI:

 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 

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 excepción de invocación de hilo cruzado.

Método auxiliar

 ///  /// Helper method to determin if invoke required, if so will rerun method on correct thread. /// if not do nothing. ///  /// Control that might require invoking /// action to preform on control thread if so. /// true if invoke required 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; } 
 this.Invoke(new MethodInvoker(delegate { //your code here; })); 

La misma pregunta: how-to-update-the-gui-from-another-thread-in-c

Dos caminos:

  1. Devuelve valor en e.result y úsalo para establecer tu valor de cuadro de texto en backgroundWorker_RunWorkerCompleted event

  2. Declare alguna variable para mantener este tipo de valores en una clase separada (que funcionará como soporte de datos). Crea una instancia estática de esta clase y puedes 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 = “text”);

Hay dos opciones para operaciones de hilo cruzado.

 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

Cross Thread Update UI | .Red

Cross Thread Update UI usando SynchronizationContext | .Red