.NETO:¿Cómo invoco a un delegado en un hilo específico?(ISynchronizeInvoke, Dispatcher, AsyncOperation, SynchronizationContext, etc.)

StackOverflow https://stackoverflow.com/questions/4843010

Pregunta

En primer lugar, tenga en cuenta que esta pregunta no está etiquetada. o o cualquier otra cosa específica de la GUI.Esto es intencional, como verá en breve.

En segundo lugar, perdón si esta pregunta es algo larga.Intento reunir varios fragmentos de información que flotan por aquí y por allá para proporcionar también información valiosa.Mi pregunta, sin embargo, está justo debajo de "Lo que me gustaría saber".

Tengo la misión de comprender finalmente las diversas formas que ofrece .NET para invocar a un delegado en un hilo específico.


Lo que me gustaría saber:

  • Estoy buscando la forma más general posible (que no sea específica de Winforms o WPF) para invocar delegados en subprocesos específicos.

  • O, dicho de otra manera:Me interesaría saber si, y cómo, las diversas formas de hacer esto (como a través de WPF) Dispatcher) hacer uso mutuo;es decir, si existe un mecanismo común para la invocación de delegados entre subprocesos que utilizan todos los demás.


Lo que ya sé:

  • Hay muchas clases relacionadas con este tema;entre ellos:

    • SynchronizationContext (en System.Threading)
      Si tuviera que adivinar, esa sería la más básica;aunque no entiendo qué hace exactamente, ni cómo se usa.

    • AsyncOperation & AsyncOperationManager (en System.ComponentModel)
      Estos parecen ser envoltorios alrededor SynchronizationContext.No tengo idea de cómo usarlos.

    • WindowsFormsSynchronizationContext (en System.Windows.Forms)
      una subclase de SynchronizationContext.

    • ISynchronizeInvoke (en System.ComponentModel)
      Utilizado por formularios de Windows.(El Control La clase implementa esto.Si tuviera que adivinar, diría que esta implementación hace uso de WindowsFormsSynchronizationContext.)

    • Dispatcher &DispatcherSynchronizationContext (en System.Windows.Threading)
      Parece que este último es otra subclase de SynchronizationContext, y los antiguos delegados en el mismo.

  • Algunos hilos tienen su propio bucle de mensajes, junto con una cola de mensajes.

    (La página de MSDN Acerca de mensajes y colas de mensajes tiene información general introductoria sobre cómo funcionan los bucles de mensajes a nivel del sistema, es decir,colas de mensajes como la API de Windows).

    Puedo ver cómo se implementaría la invocación entre subprocesos para subprocesos con una cola de mensajes.Usando la API de Windows, colocaría un mensaje en la cola de mensajes de un hilo específico a través de PostThreadMessage que contiene una instrucción para llamar a algún delegado.El bucle de mensajes, que se ejecuta en ese hilo, eventualmente llegará a ese mensaje y se llamará al delegado.

    Por lo que he leído en MSDN, un hilo no tiene automáticamente su propia cola de mensajes.Una cola de mensajes estará disponible, p.cuando un hilo ha creado una ventana.Sin una cola de mensajes, no tiene sentido que un hilo tenga un bucle de mensajes.

    Entonces, ¿es posible la invocación de delegados entre subprocesos cuando el subproceso de destino no tiene un bucle de mensajes?Digamos, ¿en una aplicación de consola .NET?(A juzgar por las respuestas a esta pregunta, Supongo que es realmente imposible con las aplicaciones de consola).

¿Fue útil?

Solución

Perdón por publicar una respuesta tan larga.Pero pensé que valía la pena explicar qué está pasando exactamente.

¡Ajá!Creo que lo tengo resuelto.La forma más genérica de invocar a un delegado en un hilo específico parece ser la SynchronizationContext clase.

Primero, el marco .NET no no proporcionar un medio predeterminado para simplemente "enviar" un delegado a cualquier hilo de modo que se ejecute allí inmediatamente.Obviamente, esto no puede funcionar, porque significaría "interrumpir" cualquier trabajo que ese hilo estuviera haciendo en ese momento.Por lo tanto, el hilo de destino decide por sí mismo cómo y cuándo "recibirá" delegados;es decir, esta funcionalidad la debe proporcionar el programador.

Entonces, un hilo de destino necesita alguna forma de "recibir" delegados.Esto se puede hacer de muchas maneras diferentes.Un mecanismo sencillo es que el hilo siempre regrese a algún bucle (llamémoslo "bucle de mensajes") donde buscará una cola.Funcionará con todo lo que esté en la cola.Windows funciona así de forma nativa cuando se trata de cosas relacionadas con la interfaz de usuario.

A continuación, demostraré cómo implementar una cola de mensajes y un SynchronizationContext para ello, así como un hilo con un bucle de mensajes.Finalmente, demostraré cómo invocar a un delegado en ese hilo.


Ejemplo:

Paso 1. primero crear un SynchronizationContext clase que se usará junto con la cola de mensajes del hilo de destino:

class QueueSyncContext : SynchronizationContext
{
    private readonly ConcurrentQueue<SendOrPostCallback> queue;

    public QueueSyncContext(ConcurrentQueue<SendOrPostCallback> queue)
    {
        this.queue = queue;
    }

    public override void Post(SendOrPostCallback d, object state)
    {
        queue.Enqueue(d);
    }

    // implementation for Send() omitted in this example for simplicity's sake.
}

Básicamente, esto no hace más que agregar todos los delegados que pasan a través de Post a una cola proporcionada por el usuario.(Post es el método para invocaciones asincrónicas. Send sería para invocaciones sincrónicas.Estoy omitiendo este último por ahora.)

Paso 2. Escribamos ahora el código para un hilo z que espera a los delegados d llegar:

SynchronizationContext syncContextForThreadZ = null;

void MainMethodOfThreadZ()
{
    // this will be used as the thread's message queue:
    var queue = new ConcurrentQueue<PostOrCallDelegate>();

    // set up a synchronization context for our message processing:
    syncContextForThreadZ = new QueueSyncContext(queue);
    SynchronizationContext.SetSynchronizationContext(syncContextForThreadZ);

    // here's the message loop (not efficient, this is for demo purposes only:)
    while (true)
    {
        PostOrCallDelegate d = null;
        if (queue.TryDequeue(out d))
        {
            d.Invoke(null);
        }
    }
}

Paso 3. Hilo z debe comenzar en alguna parte:

new Thread(new ThreadStart(MainMethodOfThreadZ)).Start();

Etapa 4. Finalmente de vuelta en algún otro hilo A, queremos enviar un delegado al hilo z:

void SomeMethodOnThreadA()
{
    // thread Z must be up and running before we can send delegates to it:
    while (syncContextForThreadZ == null) ;

    syncContextForThreadZ.Post(_ =>
        {
            Console.WriteLine("This will run on thread Z!");
        },
        null);
}

Lo bueno de esto es que SynchronizationContext funciona, sin importar si está en una aplicación de Windows Forms, en una aplicación WPF o en una aplicación de consola multiproceso creada por usted mismo.Tanto Winforms como WPF proporcionan e instalan SynchronizationContexts para su hilo principal/UI.

El procedimiento general para invocar a un delegado en un hilo específico es el siguiente:

  • Debes capturar el hilo de destino (z's) SynchronizationContext, Para que puedas Send (sincrónicamente) o Post (asincrónicamente) un delegado a ese hilo.La forma de hacer esto es almacenar el contexto de sincronización devuelto por SynchronizationContext.Current mientras estás en el hilo objetivo z.(Este contexto de sincronización debe haber sido registrado previamente en/por hilo z.) Luego almacene esa referencia en algún lugar donde sea accesible por hilo A.

  • Mientras está en el hilo A, puede utilizar el contexto de sincronización capturado para enviar o publicar cualquier delegado en el hilo z: zSyncContext.Post(_ => { ... }, null);

Otros consejos

Si desea admitir la llamada a un delegado en un hilo que de otra manera no tiene un bucle de mensaje, debe implementar el suyo, básicamente.

No hay nada particularmente mágico en un bucle de mensajes: es como un consumidor en un patrón normal de productor / consumidor. Mantiene una cola de cosas por hacer (generalmente eventos a los que reaccionar) y pasa por la cola actuando en consecuencia. Cuando no queda nada por hacer, espera hasta que se coloque algo en la cola.

Para decirlo de otra manera: puede pensar en un hilo con un bucle de mensaje como un grupo de hilos de un solo hilo.

Puede implementar esto usted mismo con bastante facilidad, incluso en una aplicación de consola. Solo recuerde que si el hilo está dando vueltas alrededor de la cola de trabajo, no puede estar haciendo otra cosa también, mientras que normalmente el hilo principal de ejecución en una aplicación de consola está destinado a realizar una secuencia de tareas y luego finalizar.

Si usa .NET 4, es muy fácil implementar una cola de productor / consumidor usando el BlockingCollection clase.

Recientemente encontré este artículo y descubrí que me salvó la vida.El uso de una cola simultánea de bloqueo es la salsa secreta, como señaló Jon Skeet anteriormente.El mejor "cómo hacer" que encontré para hacer que todo este trabajo sea este artículo sobre CodeProject de Mike Peretz.El artículo es parte de una serie de tres partes sobre SynchronizationContext que proporciona ejemplos de código que se pueden convertir fácilmente en código de producción.Tenga en cuenta que Peretz solo completa todos los detalles, pero también nos recuerda que el SynchronizationContext base tiene implementaciones esencialmente sin valor de Post () y Send () y, por lo tanto, realmente debería verse como una clase base abstracta.Un usuario ocasional de la clase base podría sorprenderse al descubrir que no resuelve los problemas del mundo real.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top