Tengo un escenario (Windows Forms, C #, .NET)
- Hay un formulario principal que aloja algún control de usuario.
- El control de usuario realiza una operación de datos pesados, 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. - Para superar esto, cargo datos en diferentes hilos (tratando de cambiar el código existente lo menos que puedo)
- Utilicé un subproceso de trabajo en segundo plano que cargará los datos y cuando termine notificará a la aplicación que ha realizado su trabajo.
- Ahora vino un verdadero problema. Toda la interfaz de usuario (formulario principal y sus controles de usuario secundarios) se creó en el subproceso 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 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, 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 ser debido a la ejecución de la línea # 1 si la condición. La tarea de carga se realiza nuevamente por el subproceso principal y no por 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 basada en el valor de un control. No quiero cambiar el valor de un control desde el hilo secundario. Nunca lo haré desde un hilo secundario.
Por lo tanto, solo acceda al valor para que los datos correspondientes se puedan obtener de la base de datos.
fuente
Respuestas:
Según el comentario de actualización de Prerak K (ya eliminado):
La solución que desea entonces debería verse así:
Realice su procesamiento serio en el hilo separado antes de intentar volver al hilo del control. Por ejemplo:
fuente
Modelo de subprocesos en la interfaz de usuario
Lea el Modelo de subprocesos en aplicaciones de IU (el enlace VB anterior está aquí ) para comprender los conceptos básicos. El enlace navega a la página que describe el modelo de subprocesos WPF. Sin embargo, Windows Forms utiliza la misma idea.
El hilo de la interfaz de usuario
Métodos BeginInvoke e Invoke
Invocar
BeginInvoke
Solución de código
Lea 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 está aquí .
fuente
Solo desea usar
Invoke
oBeginInvoke
para el mínimo trabajo requerido para cambiar la interfaz de usuario. Su método "pesado" debe ejecutarse en otro hilo (por ejemplo, víaBackgroundWorker
) pero luego usarControl.Invoke
/Control.BeginInvoke
solo para actualizar la interfaz de usuario. De esa manera, su hilo de IU será libre de manejar eventos de IU, etc.Vea mi artículo de subprocesos para ver un ejemplo de WinForms , aunque el artículo fue escrito antes de
BackgroundWorker
llegar a la escena, y me temo que no lo he actualizado a ese respecto.BackgroundWorker
simplemente simplifica un poco la devolución de llamada.fuente
Sé que es demasiado tarde ahora. Sin embargo, incluso hoy si tiene problemas para acceder a los controles de subprocesos cruzados? Esta es la respuesta más corta hasta la fecha: P
Así es como accedo a cualquier control de formulario desde un hilo.
fuente
Invoke or BeginInvoke cannot be called on a control until the window handle has been created
. Lo resolví aquíHe tenido este problema con
FileSystemWatcher
y descubrí que el siguiente código resolvió el problema:fsw.SynchronizingObject = this
El control luego usa el objeto de formulario actual para lidiar con los eventos y, por lo tanto, estará en el mismo hilo.
fuente
.SynchronizingObject = Me
Encuentro 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:
Y luego simplemente puedes hacer esto:
No más perder el tiempo, simple.
fuente
t
en este caso serátextbox1
- se pasa como argumentoLos controles en .NET generalmente no son seguros para subprocesos. Eso significa que no debe acceder a un control desde un hilo que no sea el que 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 devolver el método de larga ejecució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á.
fuente
La solución más limpia (y adecuada) para los problemas de subprocesamiento cruzado de la interfaz de usuario es usar SynchronizationContext, consulte Sincronizar llamadas a la interfaz de usuario en un artículo de aplicación multiproceso , lo explica muy bien.
fuente
Una nueva apariencia usando Async / Await y callbacks. Solo necesita una línea de código si mantiene el método de extensión en su proyecto.
Puede agregar otras cosas al método de Extensión, como envolverlo en una instrucción Try / Catch, lo que permite que la persona que llama le diga qué tipo devolver después de la finalización, una devolución de llamada de excepción a la persona que llama:
Agregar Try Catch, Auto Exception Logging y CallBack
fuente
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
en
Form1()
constructor.fuente
Siga la forma más simple (en mi opinión) para modificar objetos de otro hilo:
fuente
Debe ver 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.
fuente
Encontré una necesidad de esto mientras programaba un controlador de aplicación monotáctil iOS-Phone en un proyecto de prototipo de estudio visual winforms fuera de xamarin stuidio. Prefiriendo programar en VS sobre xamarin studio tanto como sea posible, 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 futuros usos.
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 cruzados detrás de cada clic de botón. Básicamente, permita 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, sería más limpio. No soy un experto en múltiples temas, avíseme si esto es defectuoso.
El formulario GUI no sabe que el controlador está ejecutando tareas asincrónicas.
fuente
Aquí hay una forma alternativa si el objeto con el que está trabajando no tiene
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
Funciona igual que antes, pero es un enfoque diferente si no tiene un objeto con invokerequired, pero tiene acceso al MainForm
fuente
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 hilos cruzados.
Método de ayuda
Uso de muestra
fuente
fuente
Por ejemplo, para obtener el texto de un control del subproceso de la interfaz de usuario:
fuente
La misma pregunta: cómo-actualizar-la-gui-de-otro-hilo-en-c
Dos caminos:
Devuelva el valor en e.result y úselo para establecer su valor de cuadro de texto en el evento backgroundWorker_RunWorkerCompleted
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:
fuente
Simplemente use esto:
fuente
Acción y; // declarado dentro de la clase
label1.Invoke (y = () => label1.Text = "texto");
fuente
Manera simple y reutilizable de solucionar este problema.
Método de extensión
Uso de muestra
fuente
Hay dos opciones para operaciones de subprocesos cruzados.
y el segundo es usar
Control.InvokeRequired solo es útil cuando los controles de trabajo heredados de la clase Control, mientras que SynchronizationContext se puede usar en cualquier lugar. Alguna información útil es como los siguientes enlaces
Interfaz de usuario de actualización de hilos cruzados | .Red
Interfaz de usuario de actualización de subprocesos cruzados utilizando SynchronizationContext | .Red
fuente