2010-05-12 23 views
8

Ich habe eine Sender Klasse, die eine Message auf ein IChannel sendet:Wie warte ich darauf, dass ein C# -Ereignis ausgelöst wird?

public class MessageEventArgs : EventArgs { 
    public Message Message { get; private set; } 
    public MessageEventArgs(Message m) { Message = m; } 
} 

public interface IChannel { 
    public event EventHandler<MessageEventArgs> MessageReceived; 
    void Send(Message m); 
} 

public class Sender { 
    public const int MaxWaitInMs = 5000; 
    private IChannel _c = ...; 

    public Message Send(Message m) { 
    _c.Send(m); 
    // wait for MaxWaitInMs to get an event from _c.MessageReceived 
    // return the message or null if no message was received in response 
    } 
} 

Wenn wir Nachrichten senden, gibt der IChannel manchmal eine Antwort je nachdem, welche Art von Message durch Anheben des MessageReceived Ereignis gesendet wurde. Die Ereignisargumente enthalten die Nachricht von Interesse.

Ich möchte Sender.Send() Methode warten auf eine kurze Zeit, um zu sehen, ob dieses Ereignis ausgelöst wird. Wenn ja, werde ich seine MessageEventArgs.Message Eigenschaft zurückgeben. Wenn nicht, gebe ich eine Null Message zurück.

Wie kann ich auf diese Weise warten? Ich würde es vorziehen, nicht die threading Beinarbeit mit ManualResetEvents und so zu tun, so auf normalen event s haften wäre optimal für mich.

+0

“. ..Preef nicht tun müssen, um die threading Beinarbeit zu tun ... "- Sollte dies interpretiert werden, wie Ihre App vollständig in einem einzigen Thread läuft? –

+0

Nein, die Verwendung von Worker-Threads und so ist in Ordnung, und sie werden an anderer Stelle in der App verwendet (z. B. erzeugt die IChannel-Implementierung einen neuen Thread zum Schreiben in einen Stream). Ich möchte jedoch bei der syntaktischen Suggestion von Delegierten und Ereignissen bleiben und nicht die untergeordnete "System.Threading" verwenden. –

+3

Delegaten und Ereignisse sind keine syntaktischen Zucker für Threading. Ereignisse werden ausgelöst und auf demselben Thread behandelt (es ist syntaktischer Zucker über einen Funktionsaufruf und funktioniert auf die gleiche Weise) –

Antwort

14

Verwenden Sie eine AutoResetEvent.

Gimme ein paar Minuten und ich werde eine Probe zusammenwerfen.

Hier ist sie:

public class Sender 
{ 
    public static readonly TimeSpan MaxWait = TimeSpan.FromMilliseconds(5000); 

    private IChannel _c; 
    private AutoResetEvent _messageReceived; 

    public Sender() 
    { 
     // initialize _c 
     this._messageReceived = new AutoResetEvent(false); 
     this._c.MessageReceived += this.MessageReceived; 
    } 

    public Message Send(Message m) 
    { 
     this._c.Send(m); 
     // wait for MaxWaitInMs to get an event from _c.MessageReceived 
     // return the message or null if no message was received in response 


     // This will wait for up to 5000 ms, then throw an exception. 
     this._messageReceived.WaitOne(MaxWait); 

     return null; 
    } 

    public void MessageReceived(object sender, MessageEventArgs e) 
    { 
     //Do whatever you need to do with the message 

     this._messageReceived.Set(); 
    } 
} 
+0

Ich weiß, dass ich dies mit einem ManualResetEvent tun kann, aber wie ich schon sagte ("Ich hätte lieber nicht die Threading-Arbeit mit' ManualResetEvents' "), ich hoffe, es gibt noch eine andere Möglichkeit. –

+0

Entschuldigung, das habe ich in deinem ursprünglichen Post verpasst. Auch wenn Sie einen Delegaten erstellen und 'BeginInvoke' aufrufen, müssen Sie immer noch eine Callback-Methode verwenden, und Sie müssen die Threads dennoch irgendwie manuell synchronisieren. Obwohl die IAsyncResult-Instanz ein WaitHandle für Sie bereitstellen sollte, was die Dinge ein wenig vereinfachen würde, aber nicht viel. – Toby

+0

Das sieht so aus, als ob es den Trick machen würde, und es ist ungefähr so ​​einfach, wie ich bekommen kann, nehme ich an. Vielen Dank! –

1

Haben Sie versucht, die Funktion so zuzuweisen, dass sie asynchron zu einem Delegaten aufruft, und dann mydelegeinstance.BeginInvoke aufruft?

Linky for reference.

Mit dem folgenden Beispiel, rufen Sie einfach

FillDataSet(ref table, ref dataset); 

und es wird wie von Zauberhand arbeiten. :)

#region DataSet manipulation 
///<summary>Fills a the distance table of a dataset</summary> 
private void FillDataSet(ref DistanceDataTableAdapter taD, ref MyDataSet ds) { 
    using (var myMRE = new ManualResetEventSlim(false)) { 
    ds.EnforceConstraints = false; 
    ds.Distance.BeginLoadData(); 
    Func<DistanceDataTable, int> distanceFill = taD.Fill; 
    distanceFill.BeginInvoke(ds.Distance, FillCallback<DistanceDataTable>, new object[] { distanceFill, myMRE }); 
    WaitHandle.WaitAll(new []{ myMRE.WaitHandle }); 
    ds.Distance.EndLoadData(); 
    ds.EnforceConstraints = true; 
    } 
} 
/// <summary> 
/// Callback used when filling a table asynchronously. 
/// </summary> 
/// <param name="result">Represents the status of the asynchronous operation.</param> 
private void FillCallback<MyDataTable>(IAsyncResult result) where MyDataTable: DataTable { 
    var state = result.AsyncState as object[]; 
    Debug.Assert((state != null) && (state.Length == 2), "State variable is either null or an invalid number of parameters were passed."); 

    var fillFunc = state[0] as Func<MyDataTable, int>; 
    var mre = state[1] as ManualResetEventSlim; 
    Debug.Assert((mre != null) && (fillFunc != null)); 
    int rowsAffected = fillFunc.EndInvoke(result); 
    Debug.WriteLine(" Rows: " + rowsAffected.ToString()); 
    mre.Set(); 
} 
+0

+1: guten Ruf, IMO. Implementiert grundsätzlich 'ManualResetEvent' für Sie. – IAbstract

+1

Ich bin mir nicht sicher, ob ich diese Antwort verstehe. Können Sie erläutern, was Sie im Zusammenhang mit diesem Beispiel meinen? –

+0

Ich werde es mit einem Beispiel aus meinem eigenen Code aktualisieren (was einwandfrei funktioniert) 2 Sekunden .. – Geoff

0

Vielleicht ist Ihr MessageReceived Methode sollte Flagge einfach Wert auf eine Eigenschaft Ihrer IChannel Schnittstelle, während die Prozedur INotifyPropertyChanged Ereignis Implementierung, so dass man geraten würde, wenn die Eigenschaft geändert wird .

Dadurch könnte Ihre Sender-Klasse eine Schleife durchlaufen, bis die maximale Wartezeit abgelaufen ist oder wenn der PropertyChanged-Ereignishandler auftritt und die Schleife erfolgreich unterbrochen wird. Wenn Ihre Schleife nicht unterbrochen wird, gilt die Nachricht als nie erhalten.

+0

Das würde funktionieren, aber es würde eine Änderung des "IChannel" -Vertrags erfordern, was weniger als wünschenswert erscheint. Aus der Sicht des Designers sollte IChannel nicht geändert werden, da sich an der Art, wie die Implementierer es verwenden, nichts geändert hat. –

-1

WaitOne ist wirklich das richtige Werkzeug für diesen Job. Kurz gesagt, Sie möchten zwischen 0 und MaxWaitInMs Millisekunden warten, bis ein Job abgeschlossen ist. Sie haben wirklich zwei Möglichkeiten, um nach Abschluss zu suchen oder die Threads mit einem Konstrukt zu synchronisieren, das beliebig lange warten kann.

Da sind Sie sich bewusst, der richtige Weg, dies zu tun, für die Nachwelt Ich werde die Polling-Version veröffentlichen:

MessageEventArgs msgArgs = null; 
var callback = (object o, MessageEventArgs args) => { 
    msgArgs = args; 
}; 

_c.MessageReceived += callback; 
_c.Send(m); 

int msLeft = MaxWaitInMs; 
while (msgArgs == null || msLeft >= 0) { 
    Thread.Sleep(100); 
    msLeft -= 100; // you should measure this instead with say, Stopwatch 
} 

_c.MessageRecieved -= callback; 
0

Nützliche Probe mit Autoreset:

using System; 
    using System.Threading; 

    class WaitOne 
    { 
     static AutoResetEvent autoEvent = new AutoResetEvent(false); 

     static void Main() 
     { 
      Console.WriteLine("Main starting."); 

      ThreadPool.QueueUserWorkItem(
       new WaitCallback(WorkMethod), autoEvent); 

      // Wait for work method to signal. 
      autoEvent.WaitOne(); 
      Console.WriteLine("Work method signaled.\nMain ending."); 
     } 

     static void WorkMethod(object stateInfo) 
     { 
      Console.WriteLine("Work starting."); 

      // Simulate time spent working. 
      Thread.Sleep(new Random().Next(100, 2000)); 

      // Signal that work is finished. 
      Console.WriteLine("Work ending."); 
      ((AutoResetEvent)stateInfo).Set(); 
     } 
    } 
Verwandte Themen