¿Cuál es la forma más sencilla de actualizar un Label
de otro Thread
?
Tengo una
Form
ejecuciónthread1
, y a partir de eso estoy comenzando otro hilo (thread2
).Mientras
thread2
está procesando algunos archivos me gustaría actualizar unaLabel
en elForm
con el estado actual dethread2
trabajo 's.
¿Cómo podría hacer eso?
c#
.net
multithreading
winforms
user-interface
CruelIO
fuente
fuente
Respuestas:
Para .NET 2.0, aquí hay un buen código que escribí que hace exactamente lo que quieres y funciona para cualquier propiedad en
Control
:Llámalo así:
Si está utilizando .NET 3.0 o superior, podría volver a escribir el método anterior como un método de extensión de la
Control
clase, que luego simplificaría la llamada a:ACTUALIZACIÓN 10/05/2010:
Para .NET 3.0, debe usar este código:
que usa expresiones LINQ y lambda para permitir una sintaxis mucho más limpia, simple y segura:
Ahora no solo se verifica el nombre de la propiedad en tiempo de compilación, el tipo de propiedad también lo es, por lo que es imposible (por ejemplo) asignar un valor de cadena a una propiedad booleana y, por lo tanto, causar una excepción en tiempo de ejecución.
Desafortunadamente, esto no impide que nadie haga cosas estúpidas como pasar
Control
la propiedad y el valor de otra persona, por lo que lo siguiente se compilará felizmente:Por lo tanto, agregué las comprobaciones de tiempo de ejecución para garantizar que la propiedad transferida realmente pertenezca a la
Control
que se llama al método. No es perfecto, pero sigue siendo mucho mejor que la versión .NET 2.0.Si alguien tiene más sugerencias sobre cómo mejorar este código para la seguridad en tiempo de compilación, ¡comente!
fuente
SetControlPropertyThreadSafe(myLabel, "Text", status)
ser llamado desde otro módulo, clase o formaLa forma más simple es un método anónimo pasado a
Label.Invoke
:Observe que
Invoke
bloquea la ejecución hasta que se complete; este es un código síncrono. La pregunta no se refiere al código asincrónico, pero hay un montón de contenido en Stack Overflow sobre la escritura de código asincrónico cuando desea obtener información al respecto.fuente
Manejo de trabajo largo
Desde .NET 4.5 y C # 5.0 , debe usar un patrón asincrónico basado en tareas (TAP) junto con asíncrono : aguarde las palabras clave en todas las áreas (incluida la GUI):
en lugar del Modelo de programación asincrónica (APM) y el Patrón asincrónico basado en eventos (EAP) (este último incluye la Clase BackgroundWorker ).
Entonces, la solución recomendada para un nuevo desarrollo es:
Implementación asincrónica de un controlador de eventos (Sí, eso es todo):
Implementación del segundo subproceso que notifica el subproceso de la interfaz de usuario:
Observe lo siguiente:
Para ver ejemplos más detallados, ver: El futuro de C #: Las cosas buenas llegan a quienes 'esperan' por Joseph Albahari .
Consulte también sobre el concepto de modelo de subprocesos de IU
Manejo de excepciones
El fragmento a continuación es un ejemplo de cómo manejar excepciones y alternar la
Enabled
propiedad del botón para evitar múltiples clics durante la ejecución en segundo plano.fuente
SecondThreadConcern.LongWork()
arroja una excepción, ¿puede ser detectado por el hilo de la interfaz de usuario? Esta es una excelente publicación, por cierto.Task.Delay(500).Wait()
? ¿Cuál es el punto de crear una tarea para simplemente bloquear el hilo actual? ¡Nunca debe bloquear un subproceso de grupo de subprocesos!Variación de la solución más simple de Marc Gravell para .NET 4:
O use Action delegate en su lugar:
Vea aquí para una comparación de los dos: MethodInvoker vs Action for Control.BeginInvoke
fuente
this.refresh()
a fuerza de invalidar y repintar la interfaz gráfica de usuario .. si es útil ..Método de extensión Fire and forget para .NET 3.5+
Esto se puede llamar usando la siguiente línea de código:
fuente
@this
es simplemente el nombre de la variable, en este caso la referencia al control actual que llama a la extensión. Podrías cambiarle el nombre a fuente, o lo que sea que flote en tu bote. Lo uso@this
, porque se refiere a 'este Control' que llama a la extensión y es consistente (al menos en mi cabeza) con el uso de la palabra clave 'this' en el código normal (sin extensión).OnUIThread
lugar deUIThread
.RunOnUiThread
. Pero eso es solo un gusto personal.Esta es la forma clásica de hacer esto:
Su hilo de trabajo tiene un evento. Su subproceso de interfaz de usuario comienza otro subproceso para hacer el trabajo y conecta ese evento de trabajo para que pueda mostrar el estado del subproceso de trabajo.
Luego, en la interfaz de usuario, debe cruzar hilos para cambiar el control real ... como una etiqueta o una barra de progreso.
fuente
La solución simple es usar
Control.Invoke
.fuente
El código de subprocesos suele tener errores y siempre es difícil de probar. No necesita escribir código de subprocesos para actualizar la interfaz de usuario desde una tarea en segundo plano. Simplemente use la clase BackgroundWorker para ejecutar la tarea y su método ReportProgress para actualizar la interfaz de usuario. Por lo general, solo informa un porcentaje completo, pero hay otra sobrecarga que incluye un objeto de estado. Aquí hay un ejemplo que solo informa un objeto de cadena:
Eso está bien si siempre quieres actualizar el mismo campo. Si tiene que realizar actualizaciones más complicadas, puede definir una clase para representar el estado de la interfaz de usuario y pasarla al método ReportProgress.
Una última cosa, asegúrese de establecer la
WorkerReportsProgress
bandera, o elReportProgress
método será completamente ignorado.fuente
backgroundWorker1_RunWorkerCompleted
.La gran mayoría de las respuestas usan, lo
Control.Invoke
cual es una condición de carrera que espera suceder . Por ejemplo, considere la respuesta aceptada:Si el usuario cierra el formulario justo antes de
this.Invoke
llamarlo (recuerde,this
es elForm
objeto),ObjectDisposedException
es probable que se active un.La solución es utilizarla
SynchronizationContext
, específicamenteSynchronizationContext.Current
como sugiere hamilton.danielb (otras respuestas dependen deSynchronizationContext
implementaciones específicas que son completamente innecesarias). Modificaría ligeramente su código para usarlo enSynchronizationContext.Post
lugar de hacerloSynchronizationContext.Send
(ya que generalmente no hay necesidad de que el hilo de trabajo espere):Tenga en cuenta que en .NET 4.0 y versiones posteriores realmente debería estar utilizando tareas para operaciones asíncronas. Vea la respuesta de n-san para el enfoque basado en tareas equivalente (usando
TaskScheduler.FromCurrentSynchronizationContext
).Finalmente, en .NET 4.5 y versiones posteriores también puede usar
Progress<T>
(que básicamente capturaSynchronizationContext.Current
después de su creación) como lo demostró Ryszard Dżegan para casos en los que la operación de larga duración necesita ejecutar código UI mientras aún funciona.fuente
Deberá asegurarse de que la actualización se realice en el hilo correcto; el hilo de la interfaz de usuario.
Para hacer esto, tendrá que invocar el controlador de eventos en lugar de llamarlo directamente.
Puedes hacer esto levantando tu evento así:
(El código está escrito aquí fuera de mi cabeza, por lo que no he verificado la sintaxis correcta, etc., pero debería hacerlo funcionar).
Tenga en cuenta que el código anterior no funcionará en proyectos de WPF, ya que los controles de WPF no implementan la
ISynchronizeInvoke
interfaz.Con el fin de asegurarse de que el código de arriba funciona con Windows Forms y WPF, y todas las demás plataformas, se puede echar un vistazo a las
AsyncOperation
,AsyncOperationManager
ySynchronizationContext
las clases.Para generar eventos fácilmente de esta manera, he creado un método de extensión, que me permite simplificar la generación de un evento simplemente llamando:
Por supuesto, también puede utilizar la clase BackGroundWorker, que resumirá este asunto por usted.
fuente
Deberá invocar el método en el hilo de la GUI. Puede hacerlo llamando a Control.Invoke.
Por ejemplo:
fuente
Debido a la trivialidad del escenario, realmente tendría la encuesta de subprocesos de la interfaz de usuario para el estado. Creo que encontrarás que puede ser bastante elegante.
El enfoque evita la operación de clasificación requerida cuando se utilizan los métodos
ISynchronizeInvoke.Invoke
yISynchronizeInvoke.BeginInvoke
. No hay nada de malo en usar la técnica de clasificación, pero hay algunas advertencias que debes tener en cuenta.BeginInvoke
demasiada frecuencia o podría sobrepasar la bomba de mensajes.Invoke
al subproceso de trabajo es una llamada de bloqueo. Se detendrá temporalmente el trabajo que se realiza en ese hilo.La estrategia que propongo en esta respuesta invierte los roles de comunicación de los hilos. En lugar de que el subproceso de trabajo empuje los datos, el subproceso de la interfaz de usuario lo busca. Este es un patrón común utilizado en muchos escenarios. Dado que todo lo que desea hacer es mostrar información de progreso del subproceso de trabajo, creo que encontrará que esta solución es una gran alternativa a la solución de cálculo de referencias. Tiene las siguientes ventajas.
Control.Invoke
o elControl.BeginInvoke
enfoque que los une estrechamente.fuente
Elapsed
evento, usa un método de miembro para poder quitar el temporizador cuando se desecha el formulario ...System.Timers.ElapsedEventHandler handler = (s, a) => { MyProgressLabel.Text = m_Text; };
y asignarlo a través dem_Timer.Elapsed += handler;
, más adelante en el contexto de disposición haciendo un ¿m_Timer.Elapsed -= handler;
estoy en lo cierto? Y para la disposición / cierre siguiendo los consejos como se discute aquí .Ninguna de las cosas Invocar en las respuestas anteriores es necesaria.
Debe mirar WindowsFormsSynchronizationContext:
fuente
Esta es similar a la solución anterior usando .NET Framework 3.0, pero resolvió el problema del soporte de seguridad en tiempo de compilación .
Usar:
El compilador fallará si el usuario pasa el tipo de datos incorrecto.
fuente
Salvete! Después de buscar esta pregunta, las respuestas de FrankG y Oregon Ghost me parecieron las más fáciles y útiles. Ahora, codifico en Visual Basic y ejecuté este fragmento a través de un convertidor; así que no estoy seguro de cómo resulta.
Tengo un formulario de diálogo llamado
form_Diagnostics,
que tiene un cuadro de texto enriquecido, llamadoupdateDiagWindow,
que estoy usando como una especie de pantalla de registro. Necesitaba poder actualizar su texto desde todos los hilos. Las líneas adicionales permiten que la ventana se desplace automáticamente a las líneas más nuevas.Y así, ahora puedo actualizar la pantalla con una línea, desde cualquier parte del programa completo de la manera que creas que funcionaría sin ningún subproceso:
Código principal (ponga esto dentro del código de clase de su formulario):
fuente
Para muchos propósitos es tan simple como esto:
"serviceGUI ()" es un método de nivel de GUI dentro del formulario (esto) que puede cambiar tantos controles como desee. Llame a "updateGUI ()" desde el otro hilo. Se pueden agregar parámetros para pasar valores, o (probablemente más rápido) usar variables de alcance de clase con bloqueos según sea necesario si existe la posibilidad de un choque entre hilos que acceden a ellos que podría causar inestabilidad. Use BeginInvoke en lugar de Invoke si el hilo que no es GUI es crítico en el tiempo (teniendo en cuenta la advertencia de Brian Gideon).
fuente
Esto en mi variación C # 3.0 de la solución de Ian Kemp:
Lo llamas así:
De lo contrario, el original es una muy buena solución.
fuente
Tenga en cuenta que
BeginInvoke()
se prefiere antes queInvoke()
porque es menos probable que cause puntos muertos (sin embargo, esto no es un problema aquí cuando solo se asigna texto a una etiqueta):Al usar
Invoke()
, está esperando que regrese el método. Ahora, puede ser que haga algo en el código invocado que tendrá que esperar al hilo, lo que puede no ser inmediatamente obvio si está oculto en algunas funciones que está llamando, lo que en sí mismo puede suceder indirectamente a través de controladores de eventos. Entonces estarías esperando el hilo, el hilo te estaría esperando y estás estancado.En realidad, esto provocó que se bloqueara parte de nuestro software lanzado. Fue bastante fácil de solucionar reemplazando
Invoke()
porBeginInvoke()
. A menos que necesite una operación sincrónica, que puede ser el caso si necesita un valor de retorno, úseloBeginInvoke()
.fuente
Cuando encontré el mismo problema, busqué ayuda de Google, pero en lugar de darme una solución simple, me confundió más al dar ejemplos de
MethodInvoker
bla, bla, bla. Así que decidí resolverlo por mi cuenta. Aquí está mi solución:Haga un delegado como este:
Puedes llamar a esta función en un nuevo hilo como este
No te confundas con
Thread(() => .....)
. Uso una función anónima o una expresión lambda cuando trabajo en un hilo. Para reducir las líneas de código, también puede usar elThreadStart(..)
método que no se supone que explique aquí.fuente
Simplemente use algo como esto:
fuente
e.ProgressPercentage
, ¿no estás ya en el hilo de la interfaz de usuario del método al que llamas esto?Puede usar el delegado ya existente
Action
:fuente
Mi versión es insertar una línea de "mantra" recursivo:
Sin argumentos:
Para una función que tiene argumentos:
Eso es todo .
Algunos argumentos : por lo general, es malo para la legibilidad del código poner {} después de una
if ()
declaración en una línea. Pero en este caso es el "mantra" de rutina. No interrumpe la legibilidad del código si este método es consistente durante el proyecto. Y ahorra su código de basura (una línea de código en lugar de cinco).Como puede ver
if(InvokeRequired) {something long}
, simplemente sabe que "esta función es segura para llamar desde otro hilo".fuente
Intenta actualizar la etiqueta usando esto
fuente
Crea una variable de clase:
Configúralo en el constructor que crea tu interfaz de usuario:
Cuando desee actualizar la etiqueta:
fuente
Debe usar invocar y delegar
fuente
La mayoría de las otras respuestas son un poco complejas para mí en esta pregunta (soy nuevo en C #), así que estoy escribiendo la mía:
Tengo una aplicación WPF y he definido un trabajador de la siguiente manera:
Problema:
Solución:
Todavía tengo que descubrir qué significa la línea anterior, pero funciona.
Para WinForms :
Solución:
fuente
La forma más fácil que pienso:
fuente
Por ejemplo, acceda a un control que no sea en el hilo actual:
Hay
lblThreshold
una etiqueta ySpeed_Threshold
es una variable global.fuente
Cuando esté en el subproceso de la interfaz de usuario, puede pedirle su programador de tareas de contexto de sincronización. Le daría un TaskScheduler que programa todo en el hilo de la interfaz de usuario.
Luego puede encadenar sus tareas para que cuando el resultado esté listo, otra tarea (que está programada en el hilo de la interfaz de usuario) lo elija y lo asigne a una etiqueta.
Esto funciona para tareas (no hilos) que son la forma preferida de escribir código concurrente ahora .
fuente
Task.Start
no suele ser una buena práctica blogs.msdn.com/b/pfxteam/archive/2012/01/14/10256832.aspxAcabo de leer las respuestas y este parece ser un tema muy candente. Actualmente estoy usando .NET 3.5 SP1 y Windows Forms.
La conocida fórmula ampliamente descrita en las respuestas anteriores que hace uso de la propiedad InvokeRequired cubre la mayoría de los casos, pero no todo el grupo.
¿Qué pasa si la manija aún no se ha creado?
La propiedad InvokeRequired , como se describe aquí (referencia de propiedad Control.InvokeRequired a MSDN) devuelve verdadero si la llamada se realizó desde un subproceso que no es el subproceso GUI, falso si la llamada se realizó desde el subproceso GUI o si el identificador fue No creado aún.
Puede encontrar una excepción si desea que otro hilo muestre y actualice un formulario modal. Debido a que desea que el formulario se muestre modalmente, puede hacer lo siguiente:
Y el delegado puede actualizar una etiqueta en la GUI:
Esto puede causar una InvalidOperationException si las operaciones antes de la actualización de la etiqueta "toman menos tiempo" (lean y lo interpretan como una simplificación) que el tiempo que tarda el hilo de interfaz gráfica de usuario para crear el formulario 's de la manija . Esto sucede dentro del método ShowDialog () .
También debe verificar el mango de esta manera:
Puede manejar la operación para llevar a cabo si el controlador aún no se ha creado: puede ignorar la actualización de la GUI (como se muestra en el código anterior) o puede esperar (más riesgoso). Esto debería responder la pregunta.
Cosas opcionales: Personalmente se me ocurrió codificar lo siguiente:
Alimento mis formularios que son actualizados por otro hilo con una instancia de este ThreadSafeGuiCommand , y defino métodos que actualizan la GUI (en mi formulario) de esta manera:
De esta manera, estoy bastante seguro de que tendré mi GUI actualizada independientemente del hilo que haga la llamada, opcionalmente esperando una cantidad de tiempo bien definida (el tiempo de espera).
fuente