C#中的委托和事件学习(续)

2019-05-12 07:23:05王冬梅

现在我们来看下如何解决这个问题,先回顾一下之前我在C#中的委托和事件一文中提到的内容,我说过,委托的定义会生成继承自MulticastDelegate的完整的类,其中包含Invoke()、BeginInvoke()和EndInvoke()方法。当我们直接调用委托时,实际上是调用了Invoke()方法,它会中断调用它的客户端,然后在客户端线程上执行所有订阅者的方法(客户端无法继续执行后面代码),最后将控制权返回客户端。注意到BeginInvoke()、EndInvoke()方法,在.Net中,异步执行的方法通常都会配对出现,并且以Begin和End作为方法的开头(最常见的可能就是Stream类的BeginRead()和EndRead()方法了)。它们用于方法的异步执行,即是在调用BeginInvoke()之后,客户端从线程池中抓取一个闲置线程,然后交由这个线程去执行订阅者的方法,而客户端线程则可以继续执行下面的代码。

BeginInvoke()接受“动态”的参数个数和类型,为什么说“动态”的呢?因为它的参数是在编译时根据委托的定义动态生成的,其中前面参数的个数和类型与委托定义中接受的参数个数和类型相同,最后两个参数分别是AsyncCallback和Object类型,对于它们更具体的内容,可以参见下一节委托和方法的异步调用部分。现在,我们仅需要对这两个参数传入null就可以了。另外还需要注意几点:

在委托类型上调用BeginInvoke()时,此委托对象只能包含一个目标方法,所以对于多个订阅者注册的情况,必须使用GetInvocationList()获得所有委托对象,然后遍历它们,分别在其上调用BeginInvoke()方法。如果直接在委托上调用BeginInvoke(),会抛出异常,提示“委托只能包含一个目标方法”。 如果订阅者的方法抛出异常,.NET会捕捉到它,但是只有在调用EndInvoke()的时候,才会将异常重新抛出。而在本例中,我们不使用EndInvoke()(因为我们不关心订阅者的执行情况),所以我们无需处理异常,因为即使抛出异常,也是在另一个线程上,不会影响到客户端线程(客户端甚至不知道订阅者发生了异常,这有时是好事有时是坏事)。 BeginInvoke()方法属于委托定义所生成的类,它既不属于MulticastDelegate也不属于Delegate基类,所以无法继续使用可重用的FireEvent()方法,我们需要进行一个向下转换,来获取到实际的委托类型。

现在我们修改一下上面的程序,使用异步调用来解决订阅者方法执行超时的情况:

class Program6 {
   static void Main(string[] args) {

     Publisher pub = new Publisher();
     Subscriber1 sub1 = new Subscriber1();
     Subscriber2 sub2 = new Subscriber2();
     Subscriber3 sub3 = new Subscriber3();

     pub.MyEvent += new EventHandler(sub1.OnEvent);
     pub.MyEvent += new EventHandler(sub2.OnEvent);
     pub.MyEvent += new EventHandler(sub3.OnEvent);

     pub.DoSomething();   // 触发事件

    Console.WriteLine("Control back to client!n"); // 返回控制权
    Console.WriteLine("Press any thing to exit...");
     Console.ReadKey();   // 暂停客户程序,提供时间供订阅者完成方法
  }
 }

public class Publisher {
   public event EventHandler MyEvent;
   public void DoSomething() {     
     // 做某些其他的事情
    Console.WriteLine("DoSomething invoked!");

     if (MyEvent != null) {
       Delegate[] delArray = MyEvent.GetInvocationList();

       foreach (Delegate del in delArray) {
         EventHandler method = (EventHandler)del;
         method.BeginInvoke(null, EventArgs.Empty, null, null);
       }
     }
   }
 }

public class Subscriber1 {
   public void OnEvent(object sender, EventArgs e) {
     Thread.Sleep(TimeSpan.FromSeconds(3));   // 模拟耗时三秒才能完成方法
    Console.WriteLine("Waited for 3 seconds, subscriber1 invoked!");
   }
 }

public class Subscriber2 {
   public void OnEvent(object sender, EventArgs e) {
     throw new Exception("Subsciber2 Failed");  // 即使抛出异常也不会影响到客户端
    //Console.WriteLine("Subscriber2 immediately Invoked!");
  }
 }

public class Subscriber3 {
   public void OnEvent(object sender, EventArgs e) {
     Thread.Sleep(TimeSpan.FromSeconds(2)); // 模拟耗时两秒才能完成方法
    Console.WriteLine("Waited for 2 seconds, subscriber3 invoked!");
   }
 }