代码之家  ›  专栏  ›  技术社区  ›  Pat

如何在.NET中只引发一次事件(尤其是匿名处理程序)?

  •  0
  • Pat  · 技术社区  · 16 年前

    我使用的是基于事件的API,希望某个事件处理方法只能调用一次。(请注意,我有另一个方法,它总是处理事件,但是我想为某些情况添加另一个处理程序,并且只执行一次。)

    是否可以/鼓励取消订阅事件处理程序内的事件?例如。

    private void OnEventRaised(object sender, EventArgs e) {
        _eventRaisingObject.EventRaised -= OnEventRaised;
        ... // Do normal code
    }
    

    在这种方法中,我应该关注什么样的跨线程问题?

    其次,当事件处理程序是匿名方法时,是否可以只调用一次?例如。

    _eventRaisingObject.EventRaised += (sender, e) => {
        // Unsubscribe?
        ... // Do normal code
    }
    

    目前,我使用的是匿名方法,只需检查一个布尔值,但我认为在完成工作后取消订阅会更好(开销和潜在的错误更少),也许这种假设是错误的。我在检查布尔值时预见到的一个问题是,如果情况需要重复(以便再次添加匿名事件处理程序),共享布尔值可能允许在不同的线程上多次调用匿名方法。

    4 回复  |  直到 16 年前
        1
  •  2
  •   Matthias    16 年前
    EventHandler handler;
    handler = (sender, e) => {
        _eventRaisingObject.EventRaised -= handler;
    
        // Do normal code
    }
    _eventRaisingObject.EventRaised += handler;
    

    应该可以工作,因为闭包捕获了处理程序和EventRaisingObject。

        2
  •  1
  •   Jeffrey L Whitledge    16 年前

    由于需要处理事件的情况可能会出现多次,而且线程是一个问题,因此我建议在提升和处理条件之间建立更大的连接。

    我不会动态地添加和删除事件处理程序。它当然可以在事件处理程序中完成,但是在多线程环境中很难实现这一功能。

    但出于同样的原因,我肯定也不会使用布尔值。

    您可以考虑创建一个队列。将一个项目放入队列,以表示事件处理程序需要响应事件,并在响应队列时将该项目从队列中取出(当然,在两侧都有适当的锁定),您可以更好地控制流程。

    也许这对你的情况不起作用,但这是值得考虑的事情。

        3
  •  1
  •   Hans Passant    16 年前

    可以在事件处理程序内取消订阅,但这里有一个显式的争用条件。另一个线程也可能正在调用事件处理程序,使处理程序被调用 之后 取消订阅是完全可能的。您需要序列化对事件委托的访问:

      public event EventHandler MyEvent;
      private object eventLock = new object();
    ...
      protected void OnMyEvent(EventArgs e) {
        lock(eventLock) {
          var handler = MyEvent;
          if (handler != null) MyEvent(this, e);
        }
      }
    

    这与保护布尔标记没有什么不同,尽管您可以通过互锁类提高效率。

        4
  •  0
  •   Alex J    16 年前

    您可以将发送者强制转换为引发事件并取消订阅的对象。

    void myRaisingObject_EventRaised(object sender, EventArgs e)
    {
      (sender as MyRaisingObject).EventRaised -= myRaisingObject_EventRaised;
    }