subprocesos - C#multihilo: invocar sin un control
multihilos en c# (7)
Solo estoy algo familiarizado con los subprocesos múltiples, ya que lo he leído pero nunca lo he usado en la práctica.
Tengo un proyecto que utiliza una biblioteca de terceros que comparte el estado de un dispositivo de entrada al generar eventos. El problema es que la forma en que se escribe la biblioteca, estos eventos se generan desde un subproceso diferente.
No es necesario que mi aplicación tenga múltiples subprocesos y me he encontrado con muchos problemas de subprocesos clásicos (los controles de IU se quejan de estar interactuando con un subproceso diferente, las colecciones que se modifican a medida que una pieza de código se repite en él, etc. .).
Solo quiero que el evento de la biblioteca de terceros se devuelva a mi hilo de interfaz de usuario. Específicamente, lo que creo que debería suceder es:
Mi clase recibe el evento y el controlador se ejecuta en un subproceso diferente al de la interfaz de usuario. Quiero detectar esta condición (como con InvokeRequired), y luego realizar el equivalente de BeginInvoke para devolver el control al subproceso de la interfaz de usuario. Luego, las notificaciones correctas pueden enviarse a la jerarquía de clases y todos mis datos solo son tocados por un hilo.
El problema es que la clase que recibe estos eventos de entrada no se deriva de Control y, por lo tanto, no tiene InvokeRequired o BeginInvoke. La razón de esto es que traté de separar limpiamente la IU y la lógica subyacente. La clase aún se está ejecutando en el subproceso de la interfaz de usuario, simplemente no tiene ninguna interfaz de usuario dentro de la clase en sí.
Ahora mismo arreglé el problema arruinando esa separación. Paso una referencia al control que mostrará los datos de mi clase y utilizará sus métodos Invoke. Parece que anula todo el propósito de separarlos porque ahora la clase subyacente tiene una dependencia directa de mi clase específica de UI.
Tal vez haya una forma de guardar una referencia al hilo que ejecutó el constructor y luego hay algo en el espacio de nombres de Subprocesos que ejecutará los comandos Invocar.
¿Hay alguna forma de evitar esto? ¿Es mi enfoque completamente equivocado?
¿Hay alguna forma de evitar esto?
Sí, la solución alternativa sería que usted creara una cola segura para subprocesos.
- Su controlador de eventos es invocado por el hilo de terceros
- Su controlador de eventos pone en cola algo (datos de eventos) en una colección (por ejemplo, una Lista) que posee
- Su controlador de eventos hace algo para señalar a su propio administrador, que hay datos en la colección para que se pueda poner en cola y procesar:
- Tu hilo podría estar esperando algo (un mutex o lo que sea); cuando su mutex es señalado por el controlador de eventos, se despierta y revisa la cola.
- Alternativamente, en lugar de ser señalizado, podría despertarse periódicamente (por ejemplo, una vez por segundo o lo que sea) y sondear la cola.
En cualquier caso, debido a que su cola está siendo escrita por dos subprocesos diferentes (el subproceso de terceros está en cola, y su hilo está en cola), debe ser una cola protegida de subprocesos.
El método de manejo podría simplemente almacenar los datos en una variable miembro de la clase. El único problema con los subprocesos se produce cuando desea actualizar los subprocesos a controles no creados en ese contexto de subprocesos. Por lo tanto, su clase genérica podría escuchar el evento y luego invocar el control real que desea actualizar con una función de delegado.
Una vez más, solo se deben invocar los controles de la interfaz de usuario que desea actualizar para que sean seguros. Hace un tiempo escribí una entrada de blog sobre " Solución simple para llamadas ilegales entre hilos en C # "
La publicación entra en más detalles, pero el quid de un enfoque muy simple (pero limitado) es mediante el uso de una función de delegado anónimo en el control de interfaz de usuario que desea actualizar:
if (label1.InvokeRequired) {
label1.Invoke(
new ThreadStart(delegate {
label1.Text = "some text changed from some thread";
}));
} else {
label1.Text = "some text changed from the form''s thread";
}
Espero que esto ayude. El InvokeRequired es técnicamente opcional, pero los controles de Invocación son bastante costosos, por lo que asegúrese de que no actualice label1.Text a través de la invocación si no es necesario.
Me acabo de encontrar en la misma situación. Sin embargo, en mi caso no pude usar SynchronizationContext.Current, porque no tenía acceso a ningún componente de la IU ni a la devolución de llamada para capturar el contexto de sincronización actual. Resulta que si el código no se está ejecutando actualmente en una bomba de mensajería de Windows Forms, SynchronizationContext.Current se establecerá en un SynchronizationContext estándar, que solo ejecutará Enviar llamadas en el hilo actual y Publicar llamadas en ThreadPool.
Encontré esta respuesta explicando los diferentes tipos de contextos de sincronización. En mi caso, la solución fue crear un nuevo objeto WindowsFormsSynchronizationContext en el subproceso que más tarde iniciaría el bombeo de mensajes usando Application.Run (). Este contexto de sincronización puede ser utilizado por otros subprocesos para ejecutar código en el subproceso de la interfaz de usuario, sin tocar nunca los componentes de la interfaz de usuario.
No necesita el control específico, cualquier control (incluido el formulario) será suficiente. Así que podrías abstraerlo un poco de la interfaz de usuario.
Si está utilizando WPF:
Necesita una referencia al objeto Dispatcher que administra el subproceso de la interfaz de usuario. Luego, puede usar el método Invoke o BeginInvoke en el objeto del despachador para programar una operación que tenga lugar en el subproceso de la interfaz de usuario.
La forma más sencilla de obtener el despachador es utilizar Application.Current.Dispatcher. Este es el despachador responsable del subproceso de la interfaz de usuario principal (y probablemente el único).
Poniendolo todo junto:
class MyClass
{
// Can be called on any thread
public ReceiveLibraryEvent(RoutedEventArgs e)
{
if (Application.Current.CheckAccess())
{
this.ReceiveLibraryEventInternal(e);
}
else
{
Application.Current.Dispatcher.Invoke(
new Action<RoutedEventArgs>(this.ReceiveLibraryEventInternal));
}
}
// Must be called on the UI thread
private ReceiveLibraryEventInternal(RoutedEventArgs e)
{
// Handle event
}
}
Use SynchronizationContext.Current , que apuntará a algo con lo que puede sincronizar.
Esto hará lo correcto ™ dependiendo del tipo de aplicación. Para una aplicación de WinForms, se ejecutará en el hilo principal de la interfaz de usuario.
Específicamente, use el método SynchronizationContext.Send , como este:
SynchronizationContext context =
SynchronizationContext.Current ?? new SynchronizationContext();
context.Send(s =>
{
// your code here
}, null);
Mira en la clase AsyncOperation
. Crea una instancia de AsyncOperation
en el subproceso al que desea llamar el controlador utilizando el método AsyncOperationManager.CreateOperation
. El argumento que uso para Create
es generalmente nulo, pero puedes configurarlo en cualquier cosa. Para llamar a un método en ese hilo, use el método AsyncOperation.Post
.