【问题标题】:Blocking and waiting for an event阻塞和等待事件
【发布时间】:2009-01-29 15:21:14
【问题描述】:

它有时想在等待事件发生时阻塞我的线程。

我通常这样做:

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

但是我真的找不到构造这样一个类的方法(我找不到将事件作为参数传递的有效方法)。有人可以帮忙吗?

要举例说明为什么这很有用,请考虑以下内容:

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 线程上运行它,但有时为了更简单的代码,这是一个值得权衡的选择。

替代方案看起来像这样:

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

我发现阅读起来要困难得多。诚然,流程并非总是如此线性,但当它是这样时,我喜欢第一种风格。

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

【问题讨论】:

  • 我很好奇您究竟为什么要这样做...您能详细说明一下吗?
  • 我还是不明白你为什么要阻塞线程而不是仅仅等待事件——这有什么作用?
  • 你解决过这个问题吗?我也遇到了同样的问题。
  • @Carlo:对不起,不。我认为没有好的解决方案。
  • 太糟糕了 =(,感谢您的快速回复!

标签: c# .net events


【解决方案1】:

我修改了 Dead.Rabit 的 EventWaiter 类来处理EventHandler<T>。因此,您可以使用 EventHandler<T> 来等待所有事件类型,这意味着您的委托类似于 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));

【讨论】:

    【解决方案2】:

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

    【讨论】:

    • 我不确定我是否理解。你的意思是传递订阅和取消订阅事件的代表吗(即 new EventWaiter( eh => { button.Click += eh; }, eh => { button.Click -= eh; } ) )那行得通,我假设,但我更喜欢更简单的东西。
    【解决方案3】:

    我已经在 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 );
        }
    }
    

    输出

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

    【讨论】:

      【解决方案4】:

      你也可以试试这个:

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

      【讨论】:

        【解决方案5】:

        我认为这些应该可以工作,没有尝试只是编码。

        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,你会发现你可以链接等待等等一些奇怪的东西。

        【讨论】:

        • EventArgs 并不是真正的问题。对于 EventArgs 类型来说,使其具有通用性是没有问题的。问题是似乎没有任何方法可以将事件传递给方法,以便您可以附加事件处理程序(它不是您附加的 EventArgs,如您的示例)。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-03-13
        • 2022-01-04
        • 1970-01-01
        • 1970-01-01
        • 2021-09-14
        • 1970-01-01
        相关资源
        最近更新 更多