阻止并等待事件

时间:2021-10-08 03:18:32

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.

这比使用在许多方法之间展开的逻辑编写的等效代码更简洁和可读。但是要实现WaitForUsbStickOrCancel(),WaitForUsbStickToBeRemoved和WaitUntilUserPressesDone()(假设我们在插入或删除usb棒时得到一个事件)我需要每次重新实现“EventWaiter”。当然,你必须要小心,不要在GUI线程上运行它,但有时这对于更简单的代码来说是值得的权衡。

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).

它与使用ShowMessage()和Form.ShowDialog()相当 - 它们也会阻塞,直到发生某些“事件”(尽管如果在gui-thread上调用它们,它们将运行消息循环)。

5 个解决方案

#1


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.

不传递事件,传递与事件处理程序签名匹配的委托。这实际上对我来说听起来很糟糕,所以要注意潜在的死锁问题。

#2


3  

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).

我修改了Dead.Rabit的类EventWaiter来处理EventHandler 。因此,您可以使用等待所有事件类型的EventHandler ,这意味着您的委托就像委托void SomeDelegate(对象发送者,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.

例如,当我注册到Windows推送通知服务时,我使用它来等待从HttpNotificationChannel获取Url。

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

#3


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.

我使用反射在LinqPad中一起工作样本,使用字符串获取对EventInfo对象的引用(在编译时检查失败时要小心)。显而易见的问题是,没有任何保证可以解雇事件,或者在EventWaiter类准备好开始阻塞之前可能会触发您期望的事件所以我不确定如果我把它放入,我会睡觉舒服一个生产应用程序

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

#4


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

#5


-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.

没有想太多,但无法弄清楚如何使它与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.

看一下MSDN ManualResetEvent,你会发现你可以把等待连接起来,所以有些奇怪的东西。

#1


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.

不传递事件,传递与事件处理程序签名匹配的委托。这实际上对我来说听起来很糟糕,所以要注意潜在的死锁问题。

#2


3  

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).

我修改了Dead.Rabit的类EventWaiter来处理EventHandler 。因此,您可以使用等待所有事件类型的EventHandler ,这意味着您的委托就像委托void SomeDelegate(对象发送者,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.

例如,当我注册到Windows推送通知服务时,我使用它来等待从HttpNotificationChannel获取Url。

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

#3


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.

我使用反射在LinqPad中一起工作样本,使用字符串获取对EventInfo对象的引用(在编译时检查失败时要小心)。显而易见的问题是,没有任何保证可以解雇事件,或者在EventWaiter类准备好开始阻塞之前可能会触发您期望的事件所以我不确定如果我把它放入,我会睡觉舒服一个生产应用程序

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

#4


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

#5


-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.

没有想太多,但无法弄清楚如何使它与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.

看一下MSDN ManualResetEvent,你会发现你可以把等待连接起来,所以有些奇怪的东西。