代码之家  ›  专栏  ›  技术社区  ›  Rasmus Faber

阻止和等待事件

  •  36
  • Rasmus Faber  · 技术社区  · 17 年前

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

    我通常这样做:

    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和WaitUntilUserPressDone()(假设插入或删除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线程上调用它们,它们会运行一个消息循环)。

    5 回复  |  直到 13 年前
        1
  •  4
  •   Kalanj Djordje Djordje    12 年前

    我修改了Dead。拉比的类事件等待处理 EventHandler<T> 。因此,您可以使用等待所有事件类型 事件处理程序<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
  •  3
  •   Ricardo Villamil    17 年前

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

        3
  •  0
  •   Dead.Rabit    13 年前

    我在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
  •  0
  •   theres    12 年前

    你也可以试试这个:

    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
  •  -7
  •   brutuscat    17 年前

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

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

    没有想太多,但不知道如何将其与nvarchar隔离。

    看看 MSDN ManualResetEvent 你会发现,你可以把等待和一些奇怪的事情联系起来。

    推荐文章