在这段代码中,我们使用Thread.Sleep()静态方法模拟了方法超时的情况。其中Subscriber1.OnEvent()需要三秒钟完成,Subscriber2.OnEvent()立即执行,Subscriber3.OnEvent需要两秒完成。这段代码完全可以正常输出,也没有异常抛出(如果有,也仅仅是该订阅者被忽略掉),下面是输出的情况:
复制代码 代码如下:
DoSomething invoked!
Waited for 3 seconds, subscriber1 invoked!
Subscriber2 immediately Invoked!
Waited for 2 seconds, subscriber2 invoked!
Control back to client!
但是这段程序在调用方法DoSomething()、打印了“DoSomething invoked”之后,触发了事件,随后必须等订阅者的三个方法全部执行完毕了之后,也就是大概5秒钟的时间,才能继续执行下面的语句,也就是打印“Control back to client”。而我们前面说过,很多情况下,尤其是远程调用的时候(比如说在Remoting中),发布者和订阅者应该是完全的松耦合,发布者不关心谁订阅了它、不关心订阅者的方法有什么返回值、不关心订阅者会不会抛出异常,当然也不关心订阅者需要多长时间才能完成订阅的方法,它只要在事件发生的那一瞬间告知订阅者事件已经发生并将相关参数传给订阅者就可以了。然后它就应该继续执行它后面的动作,在本例中就是打印“Control back to client!”。而订阅者不管失败或是超时都不应该影响到发布者,但在上面的例子中,发布者却不得不等待订阅者的方法执行完毕才能继续运行。
现在我们来看下如何解决这个问题,先回顾一下之前我在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!"); } }
运行上面的代码,会得到下面的输出:
复制代码 代码如下: