事件处理程序的调用

7
我有以下的事件处理程序:
private EventHandler<MyEventArgs> _myEventHandler;
public event EventHandler<MyEventArgs> MyEvent
{
  add { _myEventHandler += value; }
  remove { _myEventHandler -= value; }
}  

有人能解释一下以下代码片段之间的区别吗?
代码片段 EventHandler (A):

//Snippet A:
if (_myEventHandler != null)
{
  _myEventHandler(new MyEventArgs());
}

代码段 BeginInvoke (B):

//Snippet B:
if (_myEventHandler != null)
{
  _myEventHandler.BeginInvoke(new MyEventArgs(), ar =>
  {
    var del = (EventHandler<MyEventArgs>)ar.AsyncState;
    del.EndInvoke(ar);
  }, _myEventHandler);
}

为了澄清:调用 EventHandler "就像它是的一样" 和使用 BeginInvoke 有什么区别?
2个回答

15

BeginInvoke 方法是异步的,这意味着它在不同的线程上引发。如果人们没有预料到这一点,它可能会很危险,并且对于事件来说相当罕见,但它也可能很有用。

此外,请注意,严格来说应该快照事件处理程序的值 - 如果(通过 Begin*)您正在处理线程,则尤其如此。

var tmp = _myEventHandler;
if(tmp != null) {
    tmp(sender, args);
}

另外 - 请注意您的事件订阅本身不是线程安全的;同样,这只有在处理多线程时才会有影响,但内置的字段事件是线程安全的:

public event EventHandler<MyEventArgs> MyEvent; // <===== done; nothing more

这里避免的问题包括:

  • 使用快照,我们避免了最后一个订阅者在空值检查和调用之间取消订阅的风险(这意味着他们可能会收到一个他们没有预期的事件,但这意味着我们不会终止提高线程)
  • 使用类似字段的事件更改,我们避免了两个线程同时执行时失去订阅/取消订阅的风险

1
它不一定在不同的线程上调用,是吗?异步调用委托仍然在同一个线程上执行,但据我所知,它会在阻塞时立即返回。 - Jeff Mercado
1
@Jeff 不是的;异步调用委托意味着它在工作线程上运行。否则它怎么可能异步运行呢?请注意,这与Control.BeginInvoke略有不同,如果您已经在UI线程上,则可能继续在同一线程上运行。 - Marc Gravell
如果被调用的委托正在进行IO操作(即阻塞),则控制权将返回到调用站点。完成后,原始线程将被中断以完成方法的其余部分。据我所知,不会创建新线程,一切都是从那里中断的。 - Jeff Mercado
如果您正在使用AsyncIO,那么这个语句就是正确的。使用EventHandlers来调用正在进行IO操作的委托不会改变EventHandler的行为。在事件处理程序上调用BeginInvoke将使用线程池。就是这样。 - jamie
@Jeff 这可能适用于像 FileStream.BeginRead 这样的东西 - 但是,在这里我们只是使用 Delegate.BeginInvoke - 这是一个通用的“运行方法”API。它对目标方法将要执行的操作一无所知,因此使用线程池来调用该方法。 - Marc Gravell
显示剩余3条评论

5
< p >BeginInvoke() 调用会立即将控件返回给调用线程,并在来自 ThreadPool 的单独线程上运行委托,因此这将是一种异步执行方式。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接