2009-01-29 11 views
35

Manchmal möchte es meinen Thread blockieren, während auf ein Ereignis gewartet wird.Blockieren und Warten auf ein Ereignis

Ich mache es in der Regel etwas wie folgt aus:

private AutoResetEvent _autoResetEvent = new AutoResetEvent(false); 

private void OnEvent(object sender, EventArgs e){ 
    _autoResetEvent.Set(); 
} 

// ... 
button.Click += OnEvent; 
try{ 
    _autoResetEvent.WaitOne(); 
} 
finally{ 
    button.Click -= OnEvent; 
} 

Es scheint jedoch, dass dies etwas sein sollte, dass ich zu einer gemeinsamen Klasse extrahieren könnte (oder vielleicht sogar etwas, das bereits im Rahmen vorhanden ist).

Ich möchte in der Lage sein, so etwas zu tun:

EventWaiter ew = new EventWaiter(button.Click); 
ew.WaitOne(); 
EventWaiter ew2 = new EventWaiter(form.Closing); 
ew2.WaitOne(); 

Aber ich kann nicht wirklich einen Weg finden, eine solche Klasse zu konstruieren (Ich kann nicht eine gute gültige Weg finden, das passieren Ereignis als Argument). Kann jemand helfen?

ein Beispiel geben, warum dies nützlich sein kann, sollten Sie etwas wie folgt aus:

var status = ShowStatusForm(); 
status.ShowInsertUsbStick(); 
bool cancelled = WaitForUsbStickOrCancel(); 
if(!cancelled){ 
    status.ShowWritingOnUsbStick(); 
    WriteOnUsbStick(); 
    status.AskUserToRemoveUsbStick(); 
    WaitForUsbStickToBeRemoved(); 
    status.ShowFinished(); 
}else{ 
    status.ShowCancelled(); 
} 
status.WaitUntilUserPressesDone(); 

Das ist viel kürzer und lesbarer als der entsprechende Code mit dem zwischen vielen Methoden verteilt Logik geschrieben. Aber um WaitForUsbStickOrCancel(), WaitForUsbStickToBeRemoved und WaitUntilUserPressesDone() zu implementieren (angenommen, dass wir ein Ereignis bekommen, wenn USB-Sticks eingefügt oder entfernt werden), muss ich jedes Mal "EventWaiter" neu implementieren. Natürlich müssen Sie darauf achten, dass Sie das nie auf dem GUI-Thread ausführen, aber manchmal ist das ein lohnender Kompromiss für den einfacheren Code.

Die Alternative wäre in etwa so aussehen:

var status = ShowStatusForm(); 
status.ShowInsertUsbStick(); 
usbHandler.Inserted += OnInserted; 
status.Cancel += OnCancel; 
//... 
void OnInserted(/*..*/){ 
    usbHandler.Inserted -= OnInserted; 
    status.ShowWritingOnUsbStick(); 
    MethodInvoker mi =() => WriteOnUsbStick(); 
    mi.BeginInvoke(WritingDone, null); 
} 
void WritingDone(/*..*/){ 
    /* EndInvoke */ 
    status.AskUserToRemoveUsbStick(); 
    usbHandler.Removed += OnRemoved; 
} 
void OnRemoved(/*..*/){ 
    usbHandler.Removed -= OnRemoved; 
    status.ShowFinished(); 
    status.Done += OnDone; 
} 
/* etc */ 

ich, dass es sehr viel schwieriger finden zu lesen. Zugegeben, es ist bei weitem nicht immer so, dass der Fluss so linear sein wird, aber wenn es so ist, mag ich den ersten Stil.

Es ist vergleichbar mit der Verwendung von ShowMessage() und Form.ShowDialog() - sie auch blockieren, bis einige "Ereignis" auftritt (obwohl sie eine Nachrichtenschleife ausführen, wenn sie auf dem GUI-Thread aufgerufen werden).

+0

Ich bin neugierig, warum genau würden Sie dies tun wollen ... können Sie das näher erläutern? –

+0

Ich verstehe immer noch nicht, warum du den Thread blockieren willst, anstatt nur auf das Event zu warten - was bewirkt das? –

+0

Hast du das jemals behoben? Ich habe das gleiche Problem. – Carlo

Antwort

3

Übergeben Sie das Ereignis nicht, übergeben Sie einen Delegaten, der der Ereignishandlersignatur entspricht. Das klingt für mich wirklich hacky, also seien Sie sich der potentiellen Probleme mit dem toten Schloss bewusst.

+0

Ich bin mir nicht sicher, ob ich das verstehe. Meinst du damit Delegierte, die Subskribenten von der Veranstaltung subskribieren oder abmelden (dh neuer EventWaiter (eh => {button.Click + = eh;}, eh => {button.Click - = eh;})) Das würde funktionieren, nehme ich an, aber ich würde etwas einfacheres bevorzugen –

-7

Ich denke, wie diese funktionieren sollten, versuchte nicht nur codiert.

public class EventWaiter<T> where T : EventArgs 
{ 
    private System.Threading.ManualResetEvent manualEvent; 

    public EventWaiter(T e) 
    { 
     manualEvent = new System.Threading.ManualResetEvent(false); 
     e += this.OnEvent; 
    } 

    public void OnEvent(object sender, EventArgs e) 
    { 
     manualEvent.Set(); 
    } 

    public void WaitOne() 
    { 
     manualEvent.WaitOne(); 
    } 

    public void Reset() 
    { 
     manualEvent.Reset(); 
    } 
} 

Haben nicht zu viel nachgedacht, aber können nicht herausfinden, wie man es von den EventArgs isoliert.

Werfen Sie einen Blick auf die MSDN ManualResetEvent und Sie werden feststellen, dass Sie die Wartezeiten und so einige seltsame Sachen irgendwie Kette können.

+1

Es ist nicht wirklich ein Problem mit den EventArgs.Es wäre kein Problem, es generisch in Bezug auf den EventArgs-Typ zu machen Es scheint keine Möglichkeit zu geben, ein Ereignis an eine Methode zu übergeben, so dass Sie einen Event-Handler anhängen können (es sind keine EventArgs, an die Sie anhängen, wie in Ihrem Beispiel) –

+0

OMG !! Ja, Sie haben recht ich nicht Ich weiß nicht, was ich dachte! – brutuscat

0

Ich habe ein funktionierendes Beispiel in LinqPad mithilfe der Reflektion zusammengeführt und einen Verweis auf das EventInfo-Objekt mit einer Zeichenfolge erhalten (Vorsicht, wenn Sie die Überprüfung der Kompilierungszeit verlieren). Das offensichtliche Problem ist, dass es keine Garantie gibt, dass ein Event jemals ausgelöst wird, oder dass das Ereignis, das erwartet wird, ausgelöst wird, bevor die EventWaiter-Klasse bereit ist, zu blockieren, so dass ich nicht sicher bin, ob ich es bequem machen würde eine Produktions-App

void Main() 
{ 
    Console.WriteLine("main thread started"); 

    var workerClass = new WorkerClassWithEvent(); 
    workerClass.PerformWork(); 

    var waiter = new EventWaiter(workerClass, "WorkCompletedEvent"); 
    waiter.WaitForEvent(TimeSpan.FromSeconds(10)); 

    Console.WriteLine("main thread continues after waiting"); 
} 

public class WorkerClassWithEvent 
{ 
    public void PerformWork() 
    { 
     var worker = new BackgroundWorker(); 
     worker.DoWork += (s, e) => 
     { 
      Console.WriteLine("threaded work started"); 
      Thread.Sleep(1000); // <= the work 
      Console.WriteLine("threaded work complete"); 
     }; 
     worker.RunWorkerCompleted += (s, e) => 
     { 
      FireWorkCompletedEvent(); 
      Console.WriteLine("work complete event fired"); 
     }; 

     worker.RunWorkerAsync(); 
    } 

    public event Action WorkCompletedEvent; 
    private void FireWorkCompletedEvent() 
    { 
     if (WorkCompletedEvent != null) WorkCompletedEvent(); 
    } 
} 

public class EventWaiter 
{ 
    private AutoResetEvent _autoResetEvent = new AutoResetEvent(false); 
    private EventInfo _event    = null; 
    private object _eventContainer   = null; 

    public EventWaiter(object eventContainer, string eventName) 
    { 
     _eventContainer = eventContainer; 
     _event = eventContainer.GetType().GetEvent(eventName); 
    } 

    public void WaitForEvent(TimeSpan timeout) 
    { 
     _event.AddEventHandler(_eventContainer, (Action)delegate { _autoResetEvent.Set(); }); 
     _autoResetEvent.WaitOne(timeout); 
    } 
} 

Ausgabe

// main thread started 
// threaded work started 
// threaded work complete 
// work complete event fired 
// main thread continues after waiting 
3

I geändert Toten.Rabit's Klasse EventWaiter behandelt EventHandler<T>. Sie können also alle Ereignistypen von EventHandler<T> warten, das heißt, Ihr Delegierter ist so etwas wie delegate void SomeDelegate(object sender, T EventsArgs).

public class EventWaiter<T> 
{ 

    private AutoResetEvent _autoResetEvent = new AutoResetEvent(false); 
    private EventInfo _event = null; 
    private object _eventContainer = null; 

    public EventWaiter(object eventContainer, string eventName) 
    { 
     _eventContainer = eventContainer; 
     _event = eventContainer.GetType().GetEvent(eventName); 
    } 

    public void WaitForEvent(TimeSpan timeout) 
    { 
     EventHandler<T> eventHandler = new EventHandler<T>((sender, args) => { _autoResetEvent.Set(); }); 
     _event.AddEventHandler(_eventContainer, eventHandler); 
     _autoResetEvent.WaitOne(timeout); 
     _event.RemoveEventHandler(_eventContainer, eventHandler); 
    } 
} 

Und zum Beispiel ich, dass URL bekommen von HttpNotificationChannel gewartet, wenn ich auf Windows-Benachrichtigungsdienst Registrierung drücken.

  HttpNotificationChannel pushChannel = new HttpNotificationChannel(channelName); 
      //ChannelUriUpdated is event 
      EventWaiter<NotificationChannelUriEventArgs> ew = new EventWaiter<NotificationChannelUriEventArgs>(pushChannel, "ChannelUriUpdated"); 
      pushChannel.Open(); 
      ew.WaitForEvent(TimeSpan.FromSeconds(30)); 
0

Sie können auch versuchen, diese:

class EventWaiter<TEventArgs> where TEventArgs : EventArgs 
{ 
    private readonly Action<EventHandler<TEventArgs>> _unsubHandler; 
    private readonly Action<EventHandler<TEventArgs>> _subHandler; 

    public EventWaiter(Action<EventHandler<TEventArgs>> subHandler, Action<EventHandler<TEventArgs>> unsubHandler) 
    { 
     _unsubHandler = unsubHandler; 
     _subHandler = subHandler; 
    } 

    protected void Handler(object sender, TEventArgs args) 
    { 
     _unsubHandler.Invoke(Handler); 
     TaskCompletionSource.SetResult(args); 
    } 

    public TEventArgs WaitOnce() 
    { 
     TaskCompletionSource = new TaskCompletionSource<TEventArgs>(); 
     _subHandler.Invoke(Handler); 
     return TaskCompletionSource.Task.Result; 
    } 

    protected TaskCompletionSource<TEventArgs> TaskCompletionSource { get; set; } 

} 

Verbrauch:

EventArgs eventArgs = new EventWaiter<EventArgs>((h) => { button.Click += new EventHandler(h); }, (h) => { button.Click -= new EventHandler(h); }).WaitOnce(); 
Verwandte Themen