36

It sometimes want to block my thread while waiting for a event to occur.

I usually do it something like this:

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

However, it seems that this should be something that I could extract to a common class (or perhaps even something that already exists in the framework).

I would like to be able to do something like this:

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

But I can't really find a way to construct such a class (I can't find a good valid way to pass the event as an argument). Can anyone help?

To give an example of why this can be useful, consider something like this:

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

This is much more concise and readable than the equivalent code written with the logic spread out between many methods. But to implement WaitForUsbStickOrCancel(), WaitForUsbStickToBeRemoved and WaitUntilUserPressesDone() (assume that the we get an event when usb sticks are inserted or removed) I need to reimplement "EventWaiter" each time. Of course you have to be careful to never run this on the GUI-thread, but sometimes that is a worthwhile tradeoff for the simpler code.

The alternative would look something like this:

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 */

I find that much harder to read. Admittedly, it is far from always that the flow will be so linear, but when it is, I like the first style.

It is comparable to using ShowMessage() and Form.ShowDialog() - they also block until some "event" occurs (though they will run a message-loop if they are called on the gui-thread).

John Saunders
  • 160,644
  • 26
  • 247
  • 397
Rasmus Faber
  • 48,631
  • 24
  • 141
  • 189

5 Answers5

4

I modified Dead.Rabit's class EventWaiter to handle EventHandler<T>. So you can use for waiting all events type of EventHandler<T>, that means your delegate is something like 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);
    }
}

And for example I use that for waiting to get Url from HttpNotificationChannel when I registering to windows push notification service.

            HttpNotificationChannel pushChannel = new HttpNotificationChannel(channelName);
            //ChannelUriUpdated is event 
            EventWaiter<NotificationChannelUriEventArgs> ew = new EventWaiter<NotificationChannelUriEventArgs>(pushChannel, "ChannelUriUpdated");
            pushChannel.Open();
            ew.WaitForEvent(TimeSpan.FromSeconds(30));
Kalanj Djordje Djordje
  • 1,162
  • 1
  • 12
  • 15
3

Don't pass the event, pass a delegate that matches the event handler signature. This actually sounds hacky to me, so be aware of potential dead lock issues.

Ricardo Villamil
  • 5,031
  • 2
  • 30
  • 26
  • I am not sure I understand. Do you mean passing delegates that subscribes and unsubscribes from the event (ie. new EventWaiter( eh => { button.Click += eh; }, eh => { button.Click -= eh; } ) ) That would work, I suppose, but I would prefer something simpler. – Rasmus Faber Jan 29 '09 at 16:31
0

I've rushed together a working sample in LinqPad using reflection, getting a reference to the EventInfo object with a string (be careful as you loose compile time checking). The obvious issue is that there is no guarentee an event will ever be fired, or that the event your expecting may be fired before the EventWaiter class is ready to start blocking so I'm not sure I'd sleep comfy if I put this in a production 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 );
    }
}

Output

// main thread started
// threaded work started
// threaded work complete
// work complete event fired
// main thread continues after waiting
Dead.Rabit
  • 1,965
  • 1
  • 28
  • 46
0

You may also try this:

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

}

Usage:

EventArgs eventArgs = new EventWaiter<EventArgs>((h) => { button.Click += new EventHandler(h); }, (h) => { button.Click -= new EventHandler(h); }).WaitOnce();
theres
  • 523
  • 4
  • 13
-7

I think like these should work, didn't tried just coded.

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

Didn't thought about too much, but can't figure out how to make it isolated from the EventArgs.

Take a look at the MSDN ManualResetEvent and you will discover that you can kind of chain the waits and so some weird stuff.

brutuscat
  • 3,139
  • 2
  • 28
  • 33
  • 1
    It is not really a problem with the EventArgs. It would be no problem to make it generic regarding the EventArgs-type. The problem is that there do not seem to be any way to pass an event to a method, so that you can attach an event-handler (it is not EventArgs you attach to as in your example). – Rasmus Faber Jan 30 '09 at 08:54