.Net之美读书系列(二):委托进阶
这次看书的知识点:
- 事件访问器
- 如果一个委托中注册了多个事件且需要获取其返回值的方法
- 委托的异常处理
- 委托处理超时的方法
- 异步委托
事件访问器
职能有:
1.对委托属性进行封装,不再直接该委托变量直接进行-=和+=的操作,
2.保护委托变量只能支持注册一个事件,不能注册多个事件
3.从而即使委托中定义了返回值也不会被覆盖
还是直接上代码吧:
public static void Main(string[] args) { Publishser publish = new Publishser(); Subscriber subber = new Subscriber(); Subscriber subber2 = new Subscriber(); publish.NumberChanged += subber.OnNumberChange; publish.NumberChanged += subber2.OnNumberChange;//会把上面的覆盖,最终只会注册最后的事件 publish.DoSomething(); Console.Read(); } } public delegate string GeneralEventHandler(); public class Publishser { //对委托属性进行封装,不再直接该委托变量直接进行-=和+=的操作, //保护委托变量只能支持注册一个事件,不能注册多个事件 //从而即使委托中定义了返回值也不会被覆盖 private GeneralEventHandler numberChanger; //传说中的事件访问器,value属性是系统字段 public event GeneralEventHandler NumberChanged { add { numberChanger = value; } remove { //即使没有赋值也不会报错 numberChanger -= value; } } public void DoSomething() { if (numberChanger != null) { //调用带返回值的委托变量 string returnvalue = numberChanger(); Console.WriteLine("返回值:" + returnvalue); } } } public class Subscriber { public string OnNumberChange() { string num = "2"; Console.WriteLine("输入" + num); return num; } }
获取委托中多个返回值
如果一个委托变量中注册了多个事件,那么需要逐个调用并且return对应的返回值,这时候可以使用List集合进行装载然后返回
public static void Main(string[] args) { Subscriber sub1 = new Subscriber(); Subscriber sbu2 = new Subscriber(); Subscriber sbu3 = new Subscriber(); Publisher pub = new Publisher(); //注册事件,注意:这里是直接对事件进行注册,并没有使用事件访问器 pub.numberChanged += sub1.sub; pub.numberChanged += sbu2.sub; pub.numberChanged += sbu3.sub; //触发事件 List<string> resultList= pub.DoSomething(10); foreach (var item in resultList) { Console.WriteLine(item); } Console.Read(); } public delegate string DoneSomeThingEventHander(int num); /// <summary> /// 发布者 /// </summary> public class Publisher { //定义事件 public event DoneSomeThingEventHander numberChanged; public List<string> DoSomething(int parmeter) { List<string> resultList = new List<string>(); if (numberChanged == null) { return resultList; } //按照调用顺序获取多路广播的列表(就是注册过的事件s) Delegate[] delArray = numberChanged.GetInvocationList(); foreach (var item in delArray) { //因为委托其实编译成一个类,而且都是继承与Delegate,所以可以强制转换为对应的委托 DoneSomeThingEventHander menthod = (DoneSomeThingEventHander)item; //读取订阅者的返回值 resultList.Add(menthod(parmeter)); } return resultList; } } /// <summary> ///订阅者 /// </summary> public class Subscriber { public string sub(int num) { Console.WriteLine(num); return num.ToString(); } }
如果直接调用事件,那么就是触发所有的注册的事件,返回值将会是最后一个事件的返回值,但是如果使用了事件的GetInvocationList()方法,那么其实就可以获取到曾经注册过的事件,然后根据注册的顺序逐一调用,就可以确定但返回值了.
委托的异常处理
如果一个委托变量,按照顺序注册了ABC个方法,调用完A后,B出错了,那么C就压根不会被执行,换句话说,3个订阅者,如果中间的订阅者报错了,那么后面的订阅者也没办法执行下去.如果直接在方法中try掉也是无法执行下去的.所以只能使用上面的GetInvocationList()获取到所有注册事件,然后逐个try一次,这样就不影响调用了.
public static void Main(string[] args) { Subscriber sub1 = new Subscriber(); Subscriber2 sbu2 = new Subscriber2(); Subscriber3 sbu3 = new Subscriber3(); Publisher pub = new Publisher(); //注册事件,注意:这里是直接对事件进行注册,并没有使用事件访问器 pub.numberChanged += sub1.sub; pub.numberChanged += sbu2.sub; pub.numberChanged += sbu3.sub; //触发事件 List<string> resultList= pub.DoSomething(10); foreach (var item in resultList) { Console.WriteLine(item); } Console.Read(); } public delegate string DoneSomeThingEventHander(int num); /// <summary> /// 发布者 /// </summary> public class Publisher { //定义事件 public event DoneSomeThingEventHander numberChanged; public List<string> DoSomething(int parmeter) { List<string> resultList = new List<string>(); if (numberChanged == null) { return resultList; } ////如果直接try掉,程序可以走下去,但是订阅者却不能订阅到事件 //try //{ // resultList.Add( numberChanged(parmeter)); //} //catch (Exception ex) //{ // return new List<string>(); //} //第二种异常的处理方式 Delegate[] delArray = numberChanged.GetInvocationList(); foreach (var item in delArray) { //这样try掉的话,就不影响其他的订阅者了 try { DoneSomeThingEventHander menthod = (DoneSomeThingEventHander)item; resultList.Add(menthod(parmeter)); } catch (Exception ex) { Console.WriteLine(ex.Message); } } return resultList; } } /// <summary> ///订阅者 /// </summary> public class Subscriber { public string sub(int num) { Console.WriteLine(num); return num.ToString(); } } /// <summary> ///订阅者2 /// </summary> public class Subscriber2 { public string sub(int num) { throw new Exception("我是订阅者2的错误"); return string.Empty; } } /// <summary> ///订阅者2 /// </summary> public class Subscriber3 { public string sub(int num) { Console.WriteLine("没报错"); return "没报错"; } } }
订阅者方法超时的处理方式(异步调用)
订阅者除了可以通过异常的方式来影响发布者以外,还可以通过另一种方式:超时。超时和异常的区别就是超时并不会影响事件的正确触发和程序的正常运行,却会导致事件触发后需要很长才能够结束。能做。因为当执行订阅者方法时(通过委托,相当于依次调用所有注册了的方法),当前线程会转去执行方法中的代码,调用方法的客户端会被中断,只有当方法执行完毕并返回时,控制权才会回到客户端,从而继续执行客户端接下来的代码.
委托的定义会生成继承自MulticastDelegate的完整的类,其中包含Invoke()、BeginInvoke()和EndInvoke()方法。Invoke()直接调用委托阻断客户端,BeginInvoke()和EndInvoke()则是一个配套的方法,BeginInvoke()执行完之后,客户端就不用再管,会启动一个闲置的线程执行订阅者的方法,
public static void Main(string[] args) { Subscriber sub1 = new Subscriber(); Subscriber2 sbu2 = new Subscriber2(); Subscriber3 sbu3 = new Subscriber3(); Publisher pub = new Publisher(); //注册事件,注意:这里是直接对事件进行注册,并没有使用事件访问器 pub.numberChanged += sub1.sub; pub.numberChanged += sbu2.sub; pub.numberChanged += sbu3.sub; //触发事件 List<string> resultList = pub.DoSomething(10); foreach (var item in resultList) { Console.WriteLine("最后输出结果的"); } Console.WriteLine("客户端的最后"); Console.Read(); } public delegate string DoneSomeThingEventHander(int num); /// <summary> /// 发布者 /// </summary> public class Publisher { public event DoneSomeThingEventHander numberChanged; public List<string> DoSomething(int parmeter) { List<string> resultList = new List<string>(); if (numberChanged == null) { return resultList; } //如果注册的事件有多个,那么只能逐个调用BeginInvoke(),否则会报错 Delegate[] delArray = numberChanged.GetInvocationList(); foreach (var item in delArray) { try { DoneSomeThingEventHander menthod = (DoneSomeThingEventHander)item; //参数为null的,书中是迟点再讨论, menthod.BeginInvoke(parmeter, null, null); } catch (Exception ex) { Console.WriteLine(ex.Message); } } return resultList; } } /// <summary> ///订阅者 /// </summary> public class Subscriber { public string sub(int num) { Thread.Sleep(1000); Console.WriteLine("睡了1秒"); return "睡了1秒"; } } /// <summary> ///订阅者2 /// </summary> public class Subscriber2 { public string sub(int num) { Thread.Sleep(2000); Console.WriteLine("睡了2秒"); return "2秒"; } } /// <summary> ///订阅者2 /// </summary> public class Subscriber3 { public string sub(int num) { Console.WriteLine("没睡"); return "没睡"; } } }
以上只是一个最简单的最简单的不阻断主线程的方法,其实完成的方法应该是配合EndInvoke()一起使用的.调用BeginInvoke()就是相当于开了一个后台线程去运行方法,只要主线程不关掉,那么后台线程也会继续执行知道它执行完成为止.
异步委托
1.使用异步委托,解决等待问题,并且如何获取委托中处理完结果的方法
class Program { static void Main(string[] args) { Console.WriteLine("客户端开启"); Calculator c = new Calculator(); //委托变量 AddEventHandler addmenthod = new AddEventHandler(c.Add); //addmenthod += c.Add;//如果要异步调用,调用的委托变量只能注册一个方法,否则会报错 //在调用委托时,begininvoke会动态组成参数,前面的参数就是方法的参数了 IAsyncResult asyncResult = addmenthod.BeginInvoke(2, 5, null, null);//不使用委托调用完后回调函数, for (int i = 1; i <= 3; i++) { Thread.Sleep(i * 1000); Console.WriteLine("{0}: Client executed {1} second( s).", Thread.CurrentThread.Name, i); } //如果想获取委托的返回值,则使用EndInvoke,如果是在同一个方法中当然可以使用,原来委托的对象,如果不是在同一个方法中,则可以通过 IAsyncResult asyncResult 这个返回值来获得委托对象 //int delegateresult = addmenthod.EndInvoke(asyncResult); //在别的方法中处理获得异步委托处理的结果(和上面的是一样的,只是在第二个地方获取返回的结果而已) int delegateresult = c.getRusult(asyncResult); Console.WriteLine("委托执行结果"+delegateresult); Console.WriteLine(Thread.CurrentThread.Name + "程序执行完成"); Console.Read(); } } //定义个对应的需要异步调用的方法的委托 public delegate int AddEventHandler(int x, int y); public class Calculator { public int Add(int x, int y) { if (Thread.CurrentThread.IsThreadPoolThread) { Thread.CurrentThread.Name = "Pool Thread"; } Console.WriteLine("方法线程开始执行"); for (int i = 1; i <= 2; i++) { Thread.Sleep(i * 1000); Console.WriteLine(Thread.CurrentThread.Name + "执行了" + i + "秒"); } Console.WriteLine("方法执行完了"); return x + y; } /// <summary> /// 定义一个方法获取返回的结果 /// </summary> /// <param name="result"></param> /// <returns></returns> public int getRusult(IAsyncResult asyncresult) { //强制转换为AsyncResult对象(多态的原因) AsyncResult result = (AsyncResult)asyncresult; //获取原来委托的对象并强制转换 AddEventHandler del = (AddEventHandler)result.AsyncDelegate; int returnresult = del.EndInvoke(asyncresult); return returnresult; } }
如果要在别的地方获取委托的结果,只需要传入委托begininvoke返回的 IAsyncResult asyncResult对象,参照 public int getRusult(IAsyncResult asyncresult)即可.
2.如果在委托没有执行完,但是又调用了委托的EndInvoke()方法的话,就会阻塞主线程,那么我们不知道什么时候才会执行调用完,这时候在调用beginInvoke的倒数第二个参数就是传入一个如果方法结束了的委托的对象了.
class Program { static void Main(string[] args) { Console.WriteLine("客户端开启"); Calculator c = new Calculator(); //委托变量 AddEventHandler addmenthod = new AddEventHandler(c.Add); int parmeter = 123; AsyncCallback callback = new AsyncCallback(c.getRusult); //这次传入倒数第二个参数,就是一个回调函数,,这个回调函数就是当委托执行完之后回调的方法,倒数第一个参数可以在委托中拿取到 //callback是一个以IAsyncResult为参数的,返回值为void的方法的委托 IAsyncResult asyncResult = addmenthod.BeginInvoke(2, 5, callback, parmeter); for (int i = 1; i <= 3; i++) { Thread.Sleep(i * 1000); Console.WriteLine("{0}: Client executed {1} second( s).", Thread.CurrentThread.Name, i); } Console.WriteLine(Thread.CurrentThread.Name + "程序执行完成"); Console.Read(); } } //定义个对应的需要异步调用的方法的委托 public delegate int AddEventHandler(int x, int y); public class Calculator { public int Add(int x, int y) { if (Thread.CurrentThread.IsThreadPoolThread) { Thread.CurrentThread.Name = "Pool Thread"; } Console.WriteLine("方法线程开始执行"); for (int i = 1; i <= 2; i++) { Thread.Sleep(i * 1000); Console.WriteLine(Thread.CurrentThread.Name + "执行了" + i + "秒"); } Console.WriteLine("方法执行完了"); return x + y; } /// <summary> /// 定义一个方法获取返回的结果 /// </summary> /// <param name="result"></param> /// <returns></returns> public void getRusult(IAsyncResult asyncresult) { //强制转换为AsyncResult对象(多态的原因) AsyncResult result = (AsyncResult)asyncresult; //获取原来委托的对象并强制转换 AddEventHandler del = (AddEventHandler)result.AsyncDelegate; int returnresult = del.EndInvoke(asyncresult); Console.WriteLine("委托执行完了,输出传入的参数"+result.AsyncState.ToString()); } }
这次就到这里,最大的收获应该是对委托的理解吧