2009-09-30 19 views
16

Tengo un hilo que lee mensajes de un conducto con nombre. Es una lectura de bloqueo, por lo que está en su propio hilo. Cuando este hilo lee un mensaje, quiero que notifique al bucle de mensaje de Windows Forms que se ejecuta en el hilo principal que un mensaje está listo. ¿Cómo puedo hacer eso? En win32 haría un PostMessage, pero esa función no parece existir en .Net (o al menos no pude encontrarlo).Enviar o publicar un mensaje en un bucle de mensaje de Windows Forms

Respuesta

15

En WinForms puede lograrlo con Control.BeginInvoke. Un ejemplo:

public class SomethingReadyNotifier 
{ 
    private readonly Control synchronizer = new Control(); 

    /// <summary> 
    /// Event raised when something is ready. The event is always raised in the 
    /// message loop of the thread where this object was created. 
    /// </summary> 
    public event EventHandler SomethingReady; 

    protected void OnSomethingReady() 
    { 
     SomethingReady?.Invoke(this, EventArgs.Empty); 
    } 

    /// <summary> 
    /// Causes the SomethingReady event to be raised on the message loop of the 
    /// thread which created this object. 
    /// </summary> 
    /// <remarks> 
    /// Can safely be called from any thread. Always returns immediately without 
    /// waiting for the event to be handled. 
    /// </remarks> 
    public void NotifySomethingReady() 
    { 
     this.synchronizer.BeginInvoke(new Action(OnSomethingReady)); 
    } 
} 

Una variante más limpia de lo anterior que no depende de WinForms sería utilizar SynchronizationContext. Llame al SynchronizationContext.Current en su hilo principal, y luego pase esa referencia al constructor de la clase que se muestra a continuación.

public class SomethingReadyNotifier 
{ 
    private readonly SynchronizationContext synchronizationContext; 

    /// <summary> 
    /// Create a new <see cref="SomethingReadyNotifier"/> instance. 
    /// </summary> 
    /// <param name="synchronizationContext"> 
    /// The synchronization context that will be used to raise 
    /// <see cref="SomethingReady"/> events. 
    /// </param> 
    public SomethingReadyNotifier(SynchronizationContext synchronizationContext) 
    { 
     this.synchronizationContext = synchronizationContext; 
    } 

    /// <summary> 
    /// Event raised when something is ready. The event is always raised 
    /// by posting on the synchronization context provided to the constructor. 
    /// </summary> 
    public event EventHandler SomethingReady; 

    private void OnSomethingReady() 
    { 
     SomethingReady?.Invoke(this, EventArgs.Empty); 
    } 

    /// <summary> 
    /// Causes the SomethingReady event to be raised. 
    /// </summary> 
    /// <remarks> 
    /// Can safely be called from any thread. Always returns immediately without 
    /// waiting for the event to be handled. 
    /// </remarks> 
    public void NotifySomethingReady() 
    { 
     this.synchronizationContext.Post(
       state => OnSomethingReady(), 
       state: null); 
     } 
    } 
+0

Como se muestra, hay una condición de carrera en el método OnSomethingReady(). El evento SomethingReady podría establecerse en nulo después de la comprobación de nulo pero antes de que se produzca el evento. Para evitar esto, siga los consejos aquí: http: //blogs.msdn.com/brada/archive/2005/01/14/353132.aspx –

+1

@wcoenen, perdóneme, pero seguí y solucioné el problema. –

18

PostMessage (y del mismo modo SendMessage) son Win32 funciones API, y por lo tanto no están asociadas directamente con .NET. .NET sin embargo tiene un buen soporte para la interoperabilidad con la API de Win32, usando llamadas P/Invoke.

Como parece que eres nuevo en la programación de Win32 .NET, this MSDN Magazine article proporciona una introducción sólida sobre el tema.

The excellent pinvoke.net website detalla cómo utilizar muchas de estas funciones de API de C#/VB.NET. See this page para PostMessage específicamente.

La declaración normalizada es la siguiente:

[DllImport("user32.dll", SetLastError = true)] 
static extern bool PostMessage(HandleRef hWnd, uint Msg, IntPtr wParam, IntPtr lParam); 

Pero como la página indica, es conveniente para envolver esta función para controlar los errores de Win32 correctamente:

void PostMessageSafe(HandleRef hWnd, uint msg, IntPtr wParam, IntPtr lParam) 
{ 
    bool returnValue = PostMessage(hWnd, msg, wParam, lParam); 
    if(!returnValue) 
    { 
     // An error occured 
     throw new Win32Exception(Marshal.GetLastWin32Error()); 
    } 
}   
+0

No necesariamente quiero utilizar PostMessage. ¿No hay una forma sencilla de .Net de hacer lo que quiero hacer? –

+0

Respuesta corta: No. –

+0

Mike tiene razón. Está utilizando el bucle de mensajes de Windows, que se basa en la API de Win32 y, por lo tanto, requiere P/Invoke. – Noldorin

4

¿En realidad querer publicar un mensaje al ciclo de mensajes o simplemente desea actualizar algún control en su Formulario, mostrar un cuadro de mensaje, etc.? Si es el primero, refiérase a la respuesta de @Noldorin. Si es el último, entonces necesita usar el método Control.Invoke() para ordenar la llamada desde su hilo de "lectura" al hilo principal de la interfaz de usuario. Esto se debe a que los controles solo se pueden actualizar por el hilo en el que se crearon.

Esto es algo bastante estándar en .NET. Consulte estos artículos de MSDN para obtener las bases:

Una vez que entienda cómo hacer esto, consulte Peter Duniho's blog de cómo mejorar en la técnica canónica.

Cuestiones relacionadas