阻止并等待事件
它有时候想阻止我的线程,等待事件发生。
我通常做这样的事情:
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; }
但是,这似乎应该是我可以提取给一个普通的类(或者甚至是框架中已经存在的东西)的东西。
我希望能够做到这样的事情:
EventWaiter ew = new EventWaiter(button.Click); ew.WaitOne(); EventWaiter ew2 = new EventWaiter(form.Closing); ew2.WaitOne();
但我无法真正find构build这样一个类的方法(我无法find一个把事件作为parameter passing的好方法)。 谁能帮忙?
为了举例说明为什么这可能是有用的,考虑这样的事情:
var status = ShowStatusForm(); status.ShowInsertUsbStick(); bool cancelled = WaitForUsbStickOrCancel(); if(!cancelled){ status.ShowWritingOnUsbStick(); WriteOnUsbStick(); status.AskUserToRemoveUsbStick(); WaitForUsbStickToBeRemoved(); status.ShowFinished(); }else{ status.ShowCancelled(); } status.WaitUntilUserPressesDone();
这比在许多方法之间传播的逻辑书写的等效代码更加简洁和可读。 但要实现WaitForUsbStickOrCancel(),WaitForUsbStickToBeRemoved和WaitUntilUserPressesDone()(假设我们得到一个事件,当usb棒被插入或删除)我需要重新实现“EventWaiter”每次。 当然,你必须小心,不要在GUI线程上运行这个,但是有时这对简单的代码是一个值得的权衡。
替代scheme看起来像这样:
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 */
我觉得很难阅读。 不可否认的是,这种stream动是非常直线的,但事实并非如此,我喜欢第一种风格。
它可以使用ShowMessage()和Form.ShowDialog() – 他们也阻塞,直到发生一些“事件”(虽然他们将运行一个消息循环,如果他们在gui线程调用)。
不要传递事件,传递与事件处理程序签名匹配的委托。 这对我来说实际上听起来很不好,所以要注意潜在的死锁问题。
我修改了Dead.Rabit的类EventWaiter来处理EventHandler<T>
。 所以你可以用它来等待EventHandler<T>
所有事件types,这意味着你的委托就像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); } }
例如,当我注册到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));
我已经使用reflection冲在一起LinqPad工作示例,获取一个string的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 ); } }
产量
// main thread started // threaded work started // threaded work complete // work complete event fired // main thread continues after waiting
你也可以试试这个:
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; } }
用法:
EventArgs eventArgs = new EventWaiter<EventArgs>((h) => { button.Click += new EventHandler(h); }, (h) => { button.Click -= new EventHandler(h); }).WaitOnce();
我觉得像这些应该工作,没有尝试只是编码。
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(); } }
没有想太多,但无法弄清楚如何使其与EventArgs隔离。
看一下MSDN ManualResetEvent ,你会发现你可以链接等待,所以一些奇怪的东西。