2009-09-30 11 views
16

Mam wątek, który odczytuje wiadomości z nazwanego potoku. Jest to czytanie blokujące, dlatego znajduje się w jego własnym wątku. Kiedy ten wątek odczytuje komunikat, chcę, aby powiadomił pętlę komunikatów Windows Forms uruchomioną w głównym wątku, że wiadomość jest gotowa. Jak mogę to zrobić? W win32 zrobię PostMessage, ale ta funkcja nie istnieje w .Net (a przynajmniej nie mogłem jej znaleźć).Wysyłaj lub wysyłaj wiadomość do pętli komunikatów Windows Forms

Odpowiedz

15

W WinForms można to osiągnąć za pomocą Control.BeginInvoke. Przykład:

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)); 
    } 
} 

Czystsze wariant powyżej której nie zależy na WinForms byłoby użyć SynchronizationContext. Zadzwoń pod numer SynchronizationContext.Current w głównym wątku, a następnie przekaż odwołanie do konstruktora klasy pokazanej poniżej.

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

Jak pokazano, istnieje warunek wyścigu w metodzie OnSomethingReady(). Zdarzenie SomethingReady może zostać ustawione na wartość NULL po sprawdzeniu wartości NULL, ale przed wydaniem zdarzenia. Aby tego uniknąć, skorzystaj z porady tutaj: http: //blogs.msdn.com/brada/archive/2005/01/14/353132.aspx –

+1

@ Wcoenen, wybacz mi, ale poszedłem naprzód i naprawiłem problem. –

18

PostMessage (i podobnie SendMessage) są funkcje Win32 API funkcji, a zatem nie są bezpośrednio związane z .NET. .NET ma jednak dobrą obsługę interakcji z API Win32, używając wywołań P/Invoke.

Jak się wydaje, jesteś nowy w tworzeniu programowania Win32. NET, this MSDN Magazine article zapewnia solidne wprowadzenie na ten temat.

The excellent pinvoke.net website szczegóły, jak korzystać z wielu z tych funkcji API z C#/VB.NET. See this page dla konkretnie PostMessage.

Standardowa deklaracja jest następujący:

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

Ale gdy strona wskazuje, że jest mądry, by zakończyć tę funkcję obsługi błędów Win32 odpowiednio:

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

Niekoniecznie chcę używać PostMessage. Czy nie ma prostego sposobu .Net robienia tego, co chcę robić? –

+0

Krótka odpowiedź: nr –

+0

Mike ma rację. Używasz pętli komunikatów systemu Windows, która jest oparta na interfejsie Win32 API, a zatem wymaga P/Invoke. – Noldorin

4

Czy faktycznie chcąc odpowiedzieć wiadomość do pętli wiadomości, czy po prostu chcesz zaktualizować niektóre formanty w formularzu, wyświetlić okno komunikatu itp.? Jeśli jest to pierwsze, odwołaj się do odpowiedzi @ Noldorina. Jeśli jest to drugie, musisz użyć metody Control.Invoke(), aby przekazać połączenie z wątku "czytania" do głównego wątku UI. Wynika to z tego, że elementy sterujące mogą być aktualizowane tylko przez wątek, w którym zostały utworzone.

Jest to dość standardowa rzecz w .NET. Odnoszą się do tych artykułów MSDN, aby uzyskać podstawy:

Kiedy zrozumiesz, jak to zrobić, patrz Peter Duniho's blog do jak poprawić w sprawie techniki kanonicznej.