中的委托和事件,本文将钻探委托和事件部分一发细节的标题

C#中的委托和事件

 

转自:http://www.tracefact.net/CSharp-Programming/Delegates-and-Events-Advanced.aspx

引言

设若你看过了
C#中的委托和事件
一文,小编想你对信托和事件早已有了一个着力的认识。但那多少个远不是寄托和事件的全体内容,还有为数不少的地点没有关联。本文将钻探委托和事件部分一发细节的标题,包括部分大家常问到的题材,以及事件访问器、分外处理、超时处理和异步方法调用等内容。

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

怎么要动用事件而不是寄托变量?


C#中的委托和事件
中,作者建议了五个为啥在品种中采取事件向外部提供格局注册,而不是直接选拔委托变量的案由。主借使从封装性和易用性上去考虑,不过还漏掉了某些,事件应该由事件公布者触发,而不应有由客户端(客户程序)来触发。那句话是怎么着看头啊?请看上边包车型大巴范例:

NOTE:留意那里术语的变型,当大家单独谈论事件,我们说公布者(publisher)、订阅者(subscriber)、客户端(client)。当我们商讨Observer形式,大家说主旨(subject)和观望者(observer)。客户端经常是含有Main()方法的Program类。

class Program {
    static void Main(string[] args) {
        Publishser pub = new Publishser();
        Subscriber sub = new Subscriber();
       
        pub.NumberChanged += new
NumberChangedEventHandler(sub.OnNumberChanged);
        pub.DoSomething();          // 应该通过DoSomething()来触发事件
        pub.NumberChanged(100);     //
但足以被这么一贯调用,对信托变量的不对劲使用
    }
}

// 定义委托
public delegate void NumberChangedEventHandler(int count);

// 定义事件公布者
public class Publishser {
    private int count;
    public NumberChanged伊夫ntHandler NumberChanged;         //
证明委托变量
    //public event NumberChanged伊芙ntHandler NumberChanged; //
声圣元个轩然大波

    public void DoSomething() {
        // 在此间达成部分干活 …

        if (NumberChanged != null) {    // 触发事件
            count++;
            NumberChanged(count);
        }
    }
}

// 定义事件订阅者
public class Subscriber {
    public void OnNumberChanged(int count) {
        Console.WriteLine(“Subscriber notified: count = {0}”, count);
    }
}

地点代码定义了一个NumberChangedEventHandler委托,然后我们创造了风浪的发布者Publisher和订阅者Subscriber。当使用委托变量时,客户端能够一贯通过信托变量触发事件,也正是直接调用pub.NumberChanged(100),那将会潜移默化到独具注册了该信托的订阅者。而事件的本意应该为在事变公布者在其自作者的某部行为中触发,比如说在方式DoSomething()中级知识分子足某些条件后触发。经过添加event关键字来公布事件,事件发表者的封装性会更好,事件只是是供别的类型订阅,而客户端不能够一直触及事件(语句pub.NumberChanged(100)不可能通过编写翻译),事件只可以在事件公布者Publisher类的内部触发(比如在措施pub.DoSomething()中),换言之,就是NumberChanged(100)语句只辛亏Publisher内部被调用。

我们能够尝尝一下,将委托变量的扬言这行代码注释掉,然后撤废上边事件证明的诠释。此时先后是心有余而力不足编写翻译的,当你使用了event关键字之后,直接在客户端触发事件那种表现,也正是直接调用pub.NumberChanged(100),是被禁止的。事件只好通过调用DoSomething()来触发。这样才是事件的原意,事件发表者的卷入才会更好。

就接近倘使大家要定义叁个数字类型,我们会利用int而不是利用object一样,给予对象过多的能力并不见得是一件好事,应该是越合适越好。即使直接行使委托变量经常不会有何样难题,但它给了客户端不应具有的力量,而使用事件,能够限制这一力量,更精确地对品种进行打包。

NOTE:此地还有1个约定俗称的规定,就是订阅事件的措施的命名,平常为“On事件名”,比如那里的OnNumberChanged。

引言

假定你看过了
C#中的委托和事件
一文,作者想你对信托和事件早已有了七个宗旨的认识。但那个远不是信托和事件的全体内容,还有好多的地点并未关联。本文将切磋委托和事件部分更是细节的题材,包罗一些我们常问到的标题,以及事件访问器、相当处理、超时处理和异步方法调用等剧情。

何以委托定义的重临值日常都为void?

固然并非必需,可是大家发现众多的寄托定义重返值都为void,为啥吗?这是因为委托变量可以供四个订阅者注册,要是定义了再次来到值,那么三个订阅者的法门都会向揭橥者再次来到数值,结果正是末端贰个再次来到的章程值将前边的再次回到值覆盖掉了,因而,实际上只好取得最终三个情势调用的再次来到值。能够运作下边的代码测试一下。除此以外,发表者和订阅者是松耦合的,公布者根本不关心何人订阅了它的风浪、为什么要订阅,更别说订阅者的再次回到值了,所以回来订阅者的法子重临值大部分状态下根本未曾须求。

class Program {
    static void Main(string[] args) {
        Publishser pub = new Publishser();
        Subscriber1 sub1 = new Subscriber1();
        Subscriber2 sub2 = new Subscriber2();
        Subscriber3 sub3 = new Subscriber3();

        pub.NumberChanged += new
GeneralEventHandler(sub1.OnNumberChanged);
        pub.NumberChanged += new
GeneralEventHandler(sub2.OnNumberChanged);
        pub.NumberChanged += new
GeneralEventHandler(sub3.OnNumberChanged);
        pub.DoSomething();          // 触发事件
    }
}

// 定义委托
public delegate string GeneralEventHandler();

// 定义事件发表者
public class Publishser {
    public event General伊芙ntHandler NumberChanged; // 声美素佳儿个事变
    public void DoSomething() {
        if (NumberChanged != null) {    // 触发事件
            string rtn = NumberChanged();
            Console.WriteLine(rtn);     //
打字与印刷重回的字符串,输出为Subscriber3
        }
    }
}

// 定义事件订阅者
public class Subscriber1 { 
    public string OnNumberChanged() {
        return “Subscriber1”;
    }
}
public class Subscriber2 { /* 略,与上好像,再次来到Subscriber2*/ }
public class Subscriber3 { /* 略,与上接近,再次来到Subscriber3*/ }

如若运营那段代码,得到的输出是Subscriber3,能够看出,只得到了最终一个注册情势的重临值。

为什么要采纳事件而不是委托变量?


C#中的委托和事件
中,笔者建议了三个为啥在档次中选用事件向外部提供格局注册,而不是直接动用委托变量的原故。首若是从封装性和易用性上去考虑,可是还漏掉了一点,事件应该由事件发表者触发,而不应有由客户端(客户程序)来触发。那句话是什么样意思啊?请看上边包车型地铁范例:

NOTE:小心那里术语的转移,当我们单独谈论事件,大家说发表者(publisher)、订阅者(subscriber)、客户端(client)。当大家谈论Observer方式,大家说大旨(subject)和观看者(observer)。客户端日常是包括Main()方法的Program类。

class Program {
    static void Main(string[] args) {
        Publishser pub = new Publishser();
        Subscriber sub = new Subscriber();
       
        pub.NumberChanged += new
NumberChangedEventHandler(sub.OnNumberChanged);
        pub.DoSomething();          //
应该经过DoSomething()来触发事件
        pub.NumberChanged(100);     // 但足以被那样一向调用,对信托变量的不稳妥使用
    }
}

// 定义委托
public delegate void NumberChangedEventHandler(int count);

// 定义事件宣布者
public class Publishser {
    private int
count;
    public NumberChanged伊芙ntHandler
NumberChanged;         // 申明委托变量
    //public event
NumberChanged伊夫ntHandler
NumberChanged; // 声贝拉米(Beingmate)个轩然大波

    public void DoSomething() {
        // 在此地成功部分办事 …

        if (NumberChanged != null) {    //
触发事件
            count++;
            NumberChanged(count);
        }
    }
}

// 定义事件订阅者
public class Subscriber {
    public void OnNumberChanged(int count) {
        Console.WriteLine(“Subscriber notified: count = {0}”,
count);
    }
}

地点代码定义了3个NumberChanged伊夫ntHandler委托,然后大家创设了事件的揭橥者Publisher和订阅者Subscriber。当使用委托变量时,客户端能够直接通过信托变量触发事件,相当于一向调用pub.NumberChanged(100),这将会影响到拥有注册了该信托的订阅者。而事件的原意应该为在事件公布者在其本人的有个别行为中触发,比如说在艺术DoSomething()中级知识分子足有些条件后触发。透过添加event关键字来发表事件,事件公布者的封装性会更好,事件仅仅是供其余品类订阅,而客户端无法一贯触及事件(语句pub.NumberChanged(100)不能够通过编写翻译),事件只可以在事变公布者Publisher类的内部触发(比如在点子pub.DoSomething()中),换言之,正是NumberChanged(100)语句只幸而Publisher内部被调用。

世家能够品尝一下,将委托变量的评释那行代码注释掉,然后裁撤上边事件注明的申明。此时程序是无能为力编写翻译的,当您利用了event关键字之后,直接在客户端触发事件那种表现,也正是一贯调用pub.NumberChanged(100),是被取缔的。事件只可以通过调用DoSomething()来触发。那样才是事件的原意,事件公布者的包裹才会更好。

就就如假如我们要定义叁个数字类型,大家会使用int而不是采纳object一样,给予对象过多的能力并不见得是一件善事,应该是越合适越好。就算向来动用委托变量平时不会有何样难点,但它给了客户端不应具有的力量,而采纳事件,能够限制这一力量,更确切地对项目进行包装。

NOTE:此间还有二个约定俗称的规定,就是订阅事件的法门的命名,平时为“On事件名”,比如此处的OnNumberChanged。

怎样让事件只允许贰个客户订阅?

个别场馆下,比如像上面,为了防止爆发“值覆盖”的图景(越多是在异步调用方法时,后边会探讨),大家恐怕想限制只同意1个客户端注册。此时如何做吗?大家能够向下边那样,将事件证明为private的,然后提供八个办法来实行挂号和撤除注册:

// 定义事件发表者
public class Publishser {
    private event General伊芙ntHandler NumberChanged;    //
声Bellamy个私有事件
    // 注册事件
    public void Register(GeneralEventHandler method) {
        NumberChanged = method;
    }
    // 裁撤注册
    public void UnRegister(GeneralEventHandler method) {
        NumberChanged -= method;
    }

    public void DoSomething() {
        // 做一些其余的作业
        if (NumberChanged != null) {    // 触发事件
            string rtn = NumberChanged();
            Console.WriteLine(“Return: {0}”, rtn);      //
打字与印刷再次来到的字符串,输出为Subscriber3
        }
    }
}

NOTE:小心上边,在UnRegister()中,没有进行别的判断就应用了NumberChanged-=method语句。那是因为固然method方法没有开始展览过注册,此行语句也不会有任何难点,不会抛出十二分,仅仅是不会发生其余效能而已。

瞩目在Register()方法中,大家运用了赋值操作符“=”,而非“+=”,通过这种艺术就制止了七个方法注册。上边包车型大巴代码固然能够成功我们的急需,不过此时大家还相应专注上边两点:

壹 、将NumberChanged评释为委托变量照旧事件都不在乎了,因为它是私家的,就算将它注明为3个寄托变量,客户端也看不到它,也就不能通过它来触发事件、调用订阅者的办法。而不得不通过Register()和UnRegister()方法来注册和撤回注册,通过调用DoSomething()方法触发事件(而不是NumberChanged自身,那在近日已经商量过了)。

② 、大家还应当发现,那里运用的、对NumberChanged委托变量的拜访形式和C#中的属性是何等类似啊?大家驾驭,在C#中数见不鲜贰个属性对应1个档次成员,而在档次的外表对成员的操作全体通过质量来形成。固然此处对信托变量的处理是相近的效用,但却运用了七个艺术来拓展模拟,有没有点子像使用质量一样来达成地方的事例吗?答案是有个别,C#中提供了一种叫事件访问器(伊芙nt
Accessor)的事物,它用来封装委托变量。如下边例子所示:

class Program {
    static void Main(string[] args) {
        Publishser pub = new Publishser();
        Subscriber1 sub1 = new Subscriber1();
        Subscriber2 sub2 = new Subscriber2();

        pub.NumberChanged -= sub1.OnNumberChanged;  // 不会有其余反响
        pub.NumberChanged += sub2.OnNumberChanged;  // 注册了sub2
        pub.NumberChanged += sub1.OnNumberChanged;  //
sub1将sub2的遮盖掉了
       
        pub.DoSomething();          // 触发事件
    }
}

// 定义委托
public delegate string GeneralEventHandler();

// 定义事件发表者
public class Publishser {
    // 声明一(Wissu)(Beingmate)个寄托变量
    private GeneralEventHandler numberChanged;
    // 事件访问器的定义
    public event GeneralEventHandler NumberChanged {
        add {
            numberChanged = value;
        }
        remove {
            numberChanged -= value;
        }
    }
   
    public void DoSomething() {
        // 做一些其余的工作
        if (numberChanged != null) {    // 通过委托变量触发事件
            string rtn = numberChanged();
            Console.WriteLine(“Return: {0}”, rtn);      //
打字与印刷重临的字符串
        }
    }
}

// 定义事件订阅者
public class Subscriber1 {
    public string OnNumberChanged() {
        Console.WriteLine(“Subscriber1 Invoked!”);
        return “Subscriber1”;
    }
}
public class Subscriber2 {/* 与上类同,略 */}
public class Subscriber3 {/* 与上类同,略 */}

位置代码中好像属性的public event General伊芙ntHandler NumberChanged
{add{…}remove{…}}语句正是事件访问器。使用了轩然大波访问器今后,在DoSomething方法中便只可以通过numberChanged委托变量来触发事件,而不可能NumberChanged事件访问器(注意它们的高低写分裂)触发,它只用于注册和收回注册。下边是代码输出:

Subscriber1 Invoked!
Return: Subscriber1

为何委托定义的再次回到值日常都为void?

尽管并非必需,不过大家发现众多的委托定义重返值都为void,为何呢?那是因为委托变量可以供五个订阅者注册,假设定义了重返值,那么七个订阅者的点子都会向公布者重临数值,结果正是背后2个赶回的法门值将前边的再次回到值覆盖掉了,因而,实际上只好获得最终三个艺术调用的再次来到值。能够运转上面包车型大巴代码测试一下。除此以外,揭橥者和订阅者是松耦合的,公布者根本不关切哪个人订阅了它的风浪、为何要订阅,更别说订阅者的重回值了,所以回来订阅者的方法再次回到值大部分状态下根本未曾须要。

class Program {
    static void Main(string[] args) {
        Publishser pub = new Publishser();
        Subscriber1 sub1 = new Subscriber1();
        Subscriber2 sub2 = new Subscriber2();
        Subscriber3 sub3 = new Subscriber3();

        pub.NumberChanged += new
GeneralEventHandler(sub1.OnNumberChanged);
        pub.NumberChanged += new
GeneralEventHandler(sub2.OnNumberChanged);
        pub.NumberChanged += new
GeneralEventHandler(sub3.OnNumberChanged);
        pub.DoSomething();          //
触发事件
    }
}

// 定义委托
public delegate string GeneralEventHandler();

// 定义事件揭橥者
public class Publishser {
    public event General伊芙ntHandler NumberChanged; // 声圣元(Dumex)个风云
    public void DoSomething() {
        if (NumberChanged != null) {    //
触发事件
            string rtn = NumberChanged();
            Console.WriteLine(rtn);     // 打字与印刷重回的字符串,输出为Subscriber3
        }
    }
}

// 定义事件订阅者
public class Subscriber1 { 
    public string OnNumberChanged() {
        return “Subscriber1”;
    }
}
public class Subscriber2 { /*
略,与上看似,再次回到Subscriber2*/ }
public class Subscriber3 { /*
略,与上类似,重回Subscriber3*/ }

若果运转那段代码,得到的出口是Subscriber3,可以观望,只获得了最终一个挂号情势的重返值。

获得五个重临值与尤其处理

于今一旦我们想要得到多个订阅者的重返值,以List<string>的款式再次来到,该如何做呢?我们应有记得委托定义在编写翻译时会生成二个后续自MulticastDelegate的类,而以此MulticastDelegate又持续自Delegate,在Delegate内部,维护了三个委托链表,链表上的每1个因素,为1个只包涵三个对象措施的寄托对象。而由此Delegate基类的GetInvocationList()静态方法,能够赢得这几个委托链表。随后大家遍历那几个链表,通过链表中的每一种委托对象来调用方法,那样就能够分级赢得每种方法的再次回到值:

class Program4 {
    static void Main(string[] args) {
        Publishser pub = new Publishser();
        Subscriber1 sub1 = new Subscriber1();
        Subscriber2 sub2 = new Subscriber2();
        Subscriber3 sub3 = new Subscriber3();

        pub.NumberChanged += new
DemoEventHandler(sub1.OnNumberChanged);
        pub.NumberChanged += new
DemoEventHandler(sub2.OnNumberChanged);
        pub.NumberChanged += new
DemoEventHandler(sub3.OnNumberChanged);

        List<string> list = pub.DoSomething(); 
//调用艺术,在情势内触发事件

        foreach (string str in list) {
            Console.WriteLine(str);
        }          
    }
}

public delegate string DemoEventHandler(int num);

// 定义事件发表者
public class Publishser {
    public event 德姆o伊芙ntHandler NumberChanged;    // 声美赞臣个风云

    public List<string> DoSomething() {
        // 做一点别的的事

        List<string> strList = new List<string>();
        if (NumberChanged == null) return strList;

        // 得到委托数组
        Delegate[] delArray = NumberChanged.GetInvocationList();

        foreach (Delegate del in delArray) {
            // 进行多少个向下转移
            DemoEventHandler method = (DemoEventHandler)del;
            strList.Add(method(100));       // 调用方法并得到重返值
        }
       
        return strList;
    }
}

// 定义事件订阅者
public class Subscriber1 {
    public string OnNumberChanged(int num) {
        Console.WriteLine(“Subscriber1 invoked, number:{0}”, num);
        return “[Subscriber1 returned]”;
    }
}
public class Subscriber3 {与地点类同,略}
public class Subscriber3 {与地点类同,略}

假设运维方面包车型客车代码,能够收获这么的输出:

Subscriber1 invoked, number:100
Subscriber2 invoked, number:100
Subscriber3 invoked, number:100
[Subscriber1 returned]
[Subscriber2 returned]
[Subscriber3 returned]

可知大家收获了多个情势的重返值。而小编辈前面说过,很多意况下委托的概念都不分包重临值,所以地点介绍的办法就像是并未什么样实际意义。其实通过那种方法来触发事件最广大的景况应该是在特别处理中,因为很有或者在触发事件时,订阅者的方法会抛出12分,而这一相当会直接影响到发表者,使得公布者程序中止,而后边订阅者的措施将不会被实践。因而大家须求添加特别处理,考虑下边一段程序:

class Program5 {
    static void Main(string[] args) {
        Publisher pub = new Publisher();
        Subscriber1 sub1 = new Subscriber1();
        Subscriber2 sub2 = new Subscriber2();
        Subscriber3 sub3 = new Subscriber3();

        pub.NumberChanged += new
DemoEventHandler(sub1.OnNumberChanged);
        pub.NumberChanged += new
DemoEventHandler(sub2.OnNumberChanged);
        pub.NumberChanged += new
DemoEventHandler(sub3.OnNumberChanged);
    }
}

public class Publisher {
    public event EventHandler MyEvent;
    public void DoSomething() {
        // 做一些别的的事体
        if (MyEvent != null) {
            try {
                MyEvent(this, EventArgs.Empty);
            } catch (Exception e) {
                Console.WriteLine(“Exception: {0}”, e.Message);
            }
        }
    }
}

public class Subscriber1 {
    public void OnEvent(object sender, EventArgs e) {
        Console.WriteLine(“Subscriber1 Invoked!”);
    }
}

public class Subscriber2 {
    public void OnEvent(object sender, EventArgs e) {
        throw new Exception(“Subscriber2 Failed”);
    }
}
public class Subscriber3 {/* 与Subsciber1类同,略*/}

专注到我们在Subscriber2中抛出了要命,同时大家在Publisher中使用了try/catch语句来拍卖万分。运转方面包车型大巴代码,大家获得的结果是:

Subscriber1 Invoked!
Exception: Subscriber2 Failed

能够见见,固然我们捕获了分外,使得程序没有尤其甘休,可是却潜移默化到了前边的订阅者,因为Subscriber3也订阅了风浪,可是却没有收受事件通报(它的措施没有被调用)。此刻,大家得以选取地点的方法,先取得委托链表,然后在遍历链表的轮回中处理非凡,大家只需求修改一下DoSomething方法就能够了:

public void DoSomething() {
    if (MyEvent != null) {
        Delegate[] delArray = MyEvent.GetInvocationList();
        foreach (Delegate del in delArray) {
            伊芙ntHandler method = (伊夫ntHandler)del;    //
强制转换为切实的信托项目
            try {
                method(this, EventArgs.Empty);
            } catch (Exception e) {
                Console.WriteLine(“Exception: {0}”, e.Message);
            }
        }
    }
}

小心到Delegate是伊夫ntHandler的基类,所以为了触发事件,先要实行1个向下的强制转换,之后才能在其上接触事件,调用全部注册对象的主意。除了使用那种方法以外,还有一种更灵敏艺术得以调用方法,它是概念在Delegate基类中的DynamicInvoke()方法:

public object DynamicInvoke(params object[] args);

那大概是调用委托最通用的方法了,适用于具有项目标信托。它接受的参数为object[],也正是说它能够将轻易数量的肆意档次作为参数,并重回单个object对象。下面的DoSomething()方法也得以改写成上边那种通用方式:

public void DoSomething() {
    // 做一些其余的事体
    if (MyEvent != null) {
        Delegate[] delArray = MyEvent.GetInvocationList();
        foreach (Delegate del in delArray) {                   
            try {
                // 使用DynamicInvoke方法触发事件
                del.DynamicInvoke(this, EventArgs.Empty);  
            } catch (Exception e) {
                Console.WriteLine(“Exception: {0}”, e.Message);
            }
        }
    }
}

瞩目现行反革命在DoSomething()方法中,大家裁撤了向实际委托项指标向下转移,以往从未有过了此外的依据特定委托项目标代码,而DynamicInvoke又有啥不可承受任何类型的参数,且重返1个object对象。所以大家全然可以将DoSomething()方法抽象出来,使它成为三个集体艺术,然后供别的类来调用,大家将那些艺术评释为静态的,然后定义在Program类中:

// 触发有个别事件,以列表方式重临全数办法的再次来到值
public static object[] FireEvent(Delegate del, params object[]
args){

    List<object> objList = new List<object>();

    if (del != null) {
        Delegate[] delArray = del.GetInvocationList();
        foreach (Delegate method in delArray) {
            try {
                // 使用DynamicInvoke方法触发事件
                object obj = method.DynamicInvoke(args);
                if (obj != null)
                    objList.Add(obj);
            } catch { }
        }
    }
    return objList.ToArray();
}

跟着,大家在DoSomething()中只要简单的调用一下以此点子就足以了:

public void DoSomething() {
    // 做一点别的的事务
    Program5.FireEvent(MyEvent, this, EventArgs.Empty);
}

注意Fire伊芙nt()方法还足以回来贰个object[]数组,那一个数组包蕴了有着订阅者方法的再次来到值。而在上头的例子中,笔者从没以身作则怎么着收获并使用那么些数组,为了节省篇幅,那里也不再赘述了,在本文附带的代码中,有至于那有的的言传身教,有趣味的情侣能够下载下来看看。

什么样让事件只允许一个客户订阅?

个别情景下,比如像上面,为了幸免发出“值覆盖”的境况(越来越多是在异步调用方法时,前面会切磋),大家兴许想限制只同意贰个客户端注册。此时如何是好吧?我们能够向下边那样,将事件评释为private的,然后提供八个法子来展开挂号和撤回注册:

// 定义事件发表者
public class Publishser {
    private event General伊芙ntHandler NumberChanged;   
// 声美素佳儿(Friso)个私家事件
    // 注册事件
    public void Register(GeneralEventHandler method) {
        NumberChanged = method;
    }
    // 废除注册
    public void UnRegister(GeneralEventHandler method)
{
        NumberChanged -= method;
    }

    public void DoSomething() {
        // 做一点别的的事务
        if (NumberChanged !=
null) {    // 触发事件
            string rtn = NumberChanged();
            Console.WriteLine(“Return: {0}”, rtn);      // 打字与印刷重临的字符串,输出为Subscriber3
        }
    }
}

NOTE:在意下边,在UnRegister()中,没有展开此外判断就应用了NumberChanged-=method语句。那是因为即便method方法没有进展过注册,此行语句也不会有其它难点,不会抛出尤其,仅仅是不会发生任何意义而已。

留神在Register()方法中,大家选用了赋值操作符“=”,而非“+=”,通过那种措施就幸免了多少个法子注册。上边的代码即便能够完结大家的内需,但是此时大家还应当注意上面两点:

一 、将NumberChanged评释为委托变量依然事件都不在乎了,因为它是私家的,即使将它表明为一个信托变量,客户端也看不到它,也就无法通过它来触发事件、调用订阅者的不二法门。而不得不通过Register()和UnRegister()方法来注册和撤回注册,通过调用DoSomething()方法触发事件(而不是NumberChanged自己,这在前面已经研讨过了)。

二 、大家还应当发现,那里运用的、对NumberChanged委托变量的拜访格局和C#中的属性是多么类似啊?大家精通,在C#中无独有偶壹本性能对应贰个档次成员,而在档次的外表对成员的操作全体通过性能来形成。就算此处对信托变量的拍卖是类似的功效,但却运用了多少个法子来拓展模拟,有没有方法像使用性质一样来完毕地点的事例吗?答案是一对,C#中提供了一种叫事件访问器(伊芙nt
Accessor)的东西,它用来封装委托变量。如上边例子所示:

class Program {
    static void Main(string[] args) {
        Publishser pub = new Publishser();
        Subscriber1 sub1 = new Subscriber1();
        Subscriber2 sub2 = new Subscriber2();

        pub.NumberChanged -= sub1.OnNumberChanged;  // 不会有任何反应
        pub.NumberChanged += sub2.OnNumberChanged;  // 注册了sub2
        pub.NumberChanged += sub1.OnNumberChanged;  // sub1将sub2的掩盖掉了
       
        pub.DoSomething();          //
触发事件
    }
}

// 定义委托
public delegate string GeneralEventHandler();

// 定义事件发表者
public class Publishser {
    // 声澳优(Ausnutria Hyproca)个寄托变量
    private GeneralEventHandler numberChanged;
    // 事件访问器的概念
    public event GeneralEventHandler NumberChanged {
        add {
            numberChanged = value;
        }
        remove {
            numberChanged -= value;
        }
    }
   
    public void DoSomething() {
        // 做一点其余的业务
        if (numberChanged !=
null) {    // 通过信托变量触发事件
            string rtn = numberChanged();
            Console.WriteLine(“Return: {0}”, rtn);      // 打字与印刷再次回到的字符串
        }
    }
}

// 定义事件订阅者
public class Subscriber1 {
    public string OnNumberChanged() {
        Console.WriteLine(“Subscriber1 Invoked!”);
        return “Subscriber1”;
    }
}
public class Subscriber2 {/* 与上类同,略 */}
public class Subscriber3 {/* 与上类同,略 */}

地点代码中就像属性的public event General伊芙ntHandler NumberChanged
{add{…}remove{…}}语句正是事件访问器。使用了事件访问器以往,在DoSomething方法中便只可以通过numberChanged委托变量来触发事件,而不可能NumberChanged事件访问器(注意它们的分寸写分裂)触发,它只用于注册和撤回注册。上面是代码输出:

Subscriber1 Invoked!
Return: Subscriber1

寄托中订阅者方法超时的拍卖

订阅者除了能够经过丰硕的办法来影响发表者以外,还能透过另一种形式:超时。一般说超时,指的是措施的履行超过有个别钦赐的年华,而那边作者将含义扩大了弹指间,凡是方法执行的流年相比较长,作者就觉着它超时了,这么些“相比长”是贰个相比模糊的概念,2秒、3秒、5秒都得以算得超时。超时和丰裕的分别正是过期并不会潜移默化事件的科学触发和程序的平常化运行,却会招致事件触发后需求非常短才能够甘休。在各样执行订阅者的法门那段中间内,客户端程序会被中断,什么也不能够做。因为当执行订阅者方法时(通过信托,相当于各类调用全数注册了的格局),当前线程会转去执行措施中的代码,调用方法的客户端会被暂停,唯有当方法执行达成并回到时,控制权才会回来客户端,从而继续执行上边包车型地铁代码。大家来看一下底下一个例子:

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(“\nControl back to client!”); // 再次回到控制权
    }

    // 触发某些事件,以列表方式再次回到全数办法的再次来到值
    public static object[] FireEvent(Delegate del, params object[]
args) {
        // 代码与上同,略
    }
}

public class Publisher {
    public event EventHandler MyEvent;
    public void DoSomething() {
        // 做一些别的的工作
        Console.WriteLine(“DoSomething invoked!”);
        Program6.Fire伊夫nt(My伊芙nt, this, EventArgs.Empty); //触发事件
    }
}

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) {
        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, subscriber2
invoked!”);
    }
}

在那段代码中,大家应用Thread.Sleep()静态方法模拟了办法超时的情景。个中Subscriber1.On伊芙nt()需求三分钟完毕,Subscriber2.On伊夫nt()即刻施行,Subscriber3.On伊芙nt须求两秒完毕。那段代码完全能够健康输出,也未曾相当抛出(假诺有,也仅仅是该订阅者被忽略掉),上边是出口的图景:

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()之后,客户端从线程池中抓取2个闲置线程,然后交由那么些线程去执行订阅者的法门,而客户端线程则能够继续执行下边包车型地铁代码。

BeginInvoke()接受“动态”的参数个数和类型,为何说“动态”的吧?因为它的参数是在编写翻译时依照委托的概念动态变化的,其中前边参数的个数和花色与信托定义中收受的参数个数和档次相同,最终五个参数分别是AsyncCallback和Object类型,对于它们更切实的内容,能够参见下一节委托和艺术的异步调用部分。现在,我们仅要求对那八个参数字传送入null就足以了。别的还需求小心几点:

  • 在信托项目上调用BeginInvoke()时,此委托对象只可以分包1个目的措施,所以对于多个订阅者注册的图景,必须接纳GetInvocationList()得到全数寄托对象,然后遍历它们,分别在其上调用BeginInvoke()方法。如若直白在委托上调用BeginInvoke(),会抛出十二分,提醒“委托只可以分包一个对象措施”。
  • 若果订阅者的方法抛出相当,.NET会捕捉到它,可是唯有在调用EndInvoke()的时候,才会将这个重新抛出。而在本例中,咱们不行使EndInvoke()(因为大家不怜惜订阅者的执市场价格况),所以大家无需处理非凡,因为就算抛出极度,也是在另3个线程上,不会影响到客户端线程(客户端照旧不精通订阅者产生了老大,那有时是好事有时是坏事)。
  • BeginInvoke()方法属于委托定义所生成的类,它既不属于MulticastDelegate也不属于Delegate基类,所以不只怕持续选拔可选择的Fire伊夫nt()方法,大家须求展开三个向下转移,来博取到实在的嘱托项目。

今昔大家修改一下地方的顺序,使用异步调用来消除订阅者方法执行超时的景观:

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!”);
    }
}

运维方面包车型地铁代码,会赢得下边包车型地铁输出:

DoSomething invoked!
Control back to client!

Press any thing to exit…

Waited for 2 seconds, subscriber3 invoked!
Waited for 3 seconds, subscriber1 invoked!

亟待小心代码输出中的多少个转移:

  1. 咱俩要求在客户端程序中调用Console.ReadKey()方法来刹车客户端,以提供丰硕的时刻来让异步方法去执行完代码,不然的话客户端的先后到此地便会运维截止,程序会退出,不会看到任何订阅者方法的输出,因为它们根本没赶趟执行实现。原因是这么的:客户端所在的线程大家平日称为主线程,而进行订阅者方法的线程来自线程池,属于后台线程(Background
    Thread),当主线程停止时,不论后台线程有没有收尾,都会退出程序。(当然还有一种前台线程(Foreground
    Thread),主线程截至后务必等前台线程也终结后先后才会脱离,关于线程的座谈能够开辟另2个硕大的宗旨,那里就不研究了)。
  2. 在打字与印刷完“Press any thing to
    exit…”之后,多个订阅者的方法会以2秒、1秒的距离展现出来,且即使大家首先登场记了subscirber1,不过却先举办了subscriber3,那是因为实施它必要的年华更短。除此以外,注意到那八个措施是并行执行的,所以进行它们的总时间是最长的不二法门所供给的岁月,相当于3秒,而不是他们的累加5秒。
  3. 犹如前边所涉及的,固然subscriber2抛出了老大,大家也尚未针对性格外进行拍卖,可是客户程序并从未意识到,程序也并未就此而搁浅。

得到五个重临值与尤其处理

今后一经大家想要获得多个订阅者的重临值,以List<string>的款型重回,该如何是好吗?大家应该记得委托定义在编写翻译时会生成多个继承自MulticastDelegate的类,而那么些MulticastDelegate又持续自Delegate,在Delegate内部,维护了二个委托链表,链表上的每1个要素,为2个只包涵三个目的措施的嘱托对象。而透过Delegate基类的GetInvocationList()静态方法,能够获得这几个委托链表。随后大家遍历那些链表,通过链表中的每种委托对象来调用方法,那样就足以分别收获每种方法的再次来到值:

class Program4 {
    static void Main(string[] args) {
        Publishser pub = new Publishser();
        Subscriber1 sub1 = new Subscriber1();
        Subscriber2 sub2 = new Subscriber2();
        Subscriber3 sub3 = new Subscriber3();

        pub.NumberChanged += new
DemoEventHandler(sub1.OnNumberChanged);
        pub.NumberChanged += new
DemoEventHandler(sub2.OnNumberChanged);
        pub.NumberChanged += new
DemoEventHandler(sub3.OnNumberChanged);

        List<string> list =
pub.DoSomething();  //调用艺术,在章程内触发事件

        foreach (string str
in list) {
            Console.WriteLine(str);
        }          
    }
}

public delegate string DemoEventHandler(int num);

// 定义事件发表者
public class Publishser {
    public event 德姆o伊夫ntHandler NumberChanged;    // 声Bellamy个事件

    public List<string> DoSomething() {
        // 做一些其余的事

        List<string> strList = new List<string>();
        if (NumberChanged == null) return strList;

        // 获得委托数组
        Delegate[] delArray =
NumberChanged.GetInvocationList();

        foreach (Delegate del in delArray) {
            // 实行七个向下更换
            DemoEventHandler
method = (DemoEventHandler)del;
            strList.Add(method(100));       // 调用方法并得到重临值
        }
       
        return strList;
    }
}

// 定义事件订阅者
public class Subscriber1 {
    public string OnNumberChanged(int num) {
        Console.WriteLine(“Subscriber1 invoked, number:{0}”, num);
        return “[Subscriber1 returned]”;
    }
}
public class Subscriber3 {与地点类同,略}
public class Subscriber3 {与地方类同,略}

要是运转方面包车型地铁代码,能够赢得这么的出口:

Subscriber1 invoked, number:100
Subscriber2 invoked, number:100
Subscriber3 invoked, number:100
[Subscriber1 returned]
[Subscriber2 returned]
[Subscriber3 returned]

看得出大家收获了八个章程的重回值。而我们前边说过,很多气象下委托的概念都不带有再次来到值,所以地点介绍的主意就好像并未怎么实际意义。其实通过那种办法来触发事件最广泛的情形相应是在10分处理中,因为很有可能在接触事件时,订阅者的方法会抛出十一分,而这一老大会一贯影响到发表者,使得发表者程序中止,而前面订阅者的方法将不会被执行。由此大家要求加上特别处理,考虑下边一段程序:

class Program5 {
    static void Main(string[] args) {
        Publisher pub = new Publisher();
        Subscriber1 sub1 = new Subscriber1();
        Subscriber2 sub2 = new Subscriber2();
        Subscriber3 sub3 = new Subscriber3();

        pub.NumberChanged += new
DemoEventHandler(sub1.OnNumberChanged);
        pub.NumberChanged += new
DemoEventHandler(sub2.OnNumberChanged);
        pub.NumberChanged += new
DemoEventHandler(sub3.OnNumberChanged);
    }
}

public class Publisher {
    public event EventHandler MyEvent;
    public void DoSomething() {
        // 做一点别的的工作
        if (MyEvent != null) {
            try {
                MyEvent(this,
EventArgs.Empty);
            } catch (Exception e) {
                Console.WriteLine(“Exception:
{0}”, e.Message);
            }
        }
    }
}

public class Subscriber1 {
    public void OnEvent(object sender, EventArgs e) {
        Console.WriteLine(“Subscriber1 Invoked!”);
    }
}

public class Subscriber2 {
    public void OnEvent(object sender, EventArgs e) {
        throw new Exception(“Subscriber2 Failed”);
    }
}
public class Subscriber3 {/* 与Subsciber1类同,略*/}

只顾到大家在Subscriber2中抛出了相当,同时我们在Publisher中应用了try/catch语句来处理十分。运转方面包车型客车代码,大家获取的结果是:

Subscriber1 Invoked!
Exception: Subscriber2 Failed

能够看出,固然大家捕获了非常,使得程序尚未格外甘休,可是却影响到了后头的订阅者,因为Subscriber3也订阅了风浪,可是却没有吸收事件通报(它的法子没有被调用)。那时候,我们能够利用地点的艺术,先拿走委托链表,然后在遍历链表的轮回中处理10分,大家只供给修改一下DoSomething方法就足以了:

public void DoSomething() {
    if (MyEvent != null) {
        Delegate[] delArray = MyEvent.GetInvocationList();
        foreach (Delegate del in delArray) {
            伊芙ntHandler method =
(伊夫ntHandler)del;    //
强制转换为实际的委托项目
            try {
                method(this,
EventArgs.Empty);
            } catch (Exception e) {
                Console.WriteLine(“Exception:
{0}”, e.Message);
            }
        }
    }
}

留意到Delegate是伊夫ntHandler的基类,所以为了触发事件,先要进行二个向下的恐吓转换,之后才能在其上接触事件,调用全部注册对象的主意。除了运用那种方法以外,还有一种更灵活艺术能够调用方法,它是概念在Delegate基类中的DynamicInvoke()方法:

public object DynamicInvoke(params object[] args);

那只怕是调用委托最通用的法子了,适用于具有品种的信托。它接受的参数为object[],也正是说它能够将轻易数量的即兴档次作为参数,并回到单个object对象。上边包车型地铁DoSomething()方法也能够改写成下边那种通用方式:

public void DoSomething() {
    // 做一点其余的事情
    if (MyEvent != null) {
        Delegate[] delArray = MyEvent.GetInvocationList();
        foreach (Delegate del in delArray) {                   
            try {
                //
使用DynamicInvoke方法触发事件
                del.DynamicInvoke(this, EventArgs.Empty);  
            } catch (Exception e) {
                Console.WriteLine(“Exception:
{0}”, e.Message);
            }
        }
    }
}

注意现行反革命在DoSomething()方法中,大家撤除了向现实委托项目标向下更换,今后从未了其他的依据特定委托项目标代码,而DynamicInvoke又足以接受别的类型的参数,且重临2个object对象。所以我们完全能够将DoSomething()方法抽象出来,使它变成三个共用艺术,然后供其余类来调用,大家将以此法子申明为静态的,然后定义在Program类中:

//
触发有个别事件,以列表情势重回全体办法的再次来到值
public static object[] FireEvent(Delegate del, params object[] args){

    List<object> objList = new List<object>();

    if (del != null) {
        Delegate[] delArray = del.GetInvocationList();
        foreach (Delegate method in delArray) {
            try {
                //
使用DynamicInvoke方法触发事件
                object obj =
method.DynamicInvoke(args);
                if (obj != null)
                    objList.Add(obj);
            } catch { }
        }
    }
    return objList.ToArray();
}

接着,我们在DoSomething()中一经简单的调用一下以此艺术就能够了:

public void DoSomething() {
    // 做一点别的的业务
    Program5.FireEvent(MyEvent, this, EventArgs.Empty);
}

在意Fire伊芙nt()方法还足以回到二个object[]数组,这么些数组包蕴了独具订阅者方法的重返值。而在地点的事例中,小编未曾以身作则如何获取并利用这几个数组,为了省去篇幅,那里也不再赘述了,在本文附带的代码中,有关于那有个别的以身作则,有趣味的爱侣能够下载下来看看。

寄托和方式的异步调用

一般状态下,假使必要异步执行四个耗费时间的操作,大家会新起1个线程,然后让那几个线程去履行代码。可是对于每三个异步调用都由此成立线程来进展操作鲜明会对品质发生一定的熏陶,同时操作也绝对繁琐一些。.Net中能够透过委托实行方式的异步调用,正是说客户端在异步调用方法时,本人并不会因为方法的调用而中止,而是从线程池中抓取二个线程去实践该方式,本人线程(主线程)在做到抓取线程这一过程之后,继续执行上边包车型地铁代码,那样就贯彻了代码的并行执行。使用线程池的补益便是幸免了频仍举办异步调用时创造、销毁线程的开销。

犹如上边所示,当大家在信托对象上调用BeginInvoke()时,便进行了三个异步的法门调用。地方的事例中是在事件的发表和订阅这一历程中采纳了异步调用,而在事件揭橥者和订阅者之间数十三遍是松耦合的,宣布者平时不须要取得订阅者方法执行的动静;而当使用异步调用时,更加多情形下是为了升高系统的属性,而毫无专用于事件的昭示和订阅这一编制程序模型。而在那种状态下使用异步编制程序时,就供给展开越多的主宰,比如当异步执行措施的法门甘休时通报客户端、重返异步执行格局的重返值等。本节就对BeginInvoke()方法、EndInvoke()方法和其有关的IAysncResult做二个简约的牵线。

NOTE:专注此处小编曾经不复利用揭橥者、订阅者那些术语,因为大家不再是商讨上边的轩然大波模型,而是切磋在客户端程序中异步地调用方法,那里有2个合计的变迁。

笔者们看那样一段代码,它以身作则了不采纳异步调用的平时情状:

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

        Console.WriteLine(“Client application started!\n”);
        Thread.CurrentThread.Name = “Main Thread”;

        Calculator cal = new Calculator();
        int result = cal.Add(2, 5);
        Console.WriteLine(“Result: {0}\n”, result);
       
        // 做一些其余的作业,模拟须要进行3分钟
        for (int i = 1; i <= 3; i++) {
            Thread.Sleep(TimeSpan.FromSeconds(i));
            Console.WriteLine(“{0}: Client executed {1} second(s).”,
                Thread.CurrentThread.Name, i); 
        }

        Console.WriteLine(“\nPress any key to exit…”);
        Console.ReadKey();
    }
}

public class Calculator {
    public int Add(int x, int y) {
        if (Thread.CurrentThread.IsThreadPoolThread) {
            Thread.CurrentThread.Name = “Pool Thread”;
        }
        Console.WriteLine(“Method invoked!”);          

        // 执行有个别事情,模拟供给实施2分钟
        for (int i = 1; i <= 2; i++) {
            Thread.Sleep(TimeSpan.FromSeconds(i));
            Console.WriteLine(“{0}: Add executed {1} second(s).”,
                Thread.CurrentThread.Name, i); 
        }
        Console.WriteLine(“Method complete!”);
        return x + y;
    }
}

上边代码有多少个有关对于线程的操作,要是不明白能够看一下下边包车型客车表明,借使您曾经明白能够一直跳过:

  • Thread.Sleep(),它会让实施当前代码的线程暂停一段时间(若是你对线程的概念比较素不相识,能够通晓为使程序的实践暂停一段时间),以皮秒为单位,比如Thread.Sleep(一千),将会使线程暂停1分钟。在上头作者利用了它的重载方法,个人觉得接纳TimeSpan.FromSeconds(1),可读性更好有的。
  • Thread.CurrentThread.Name,通过那特特性能够安装、获取执行当前代码的线程的名称,值得注意的是其一个性只可以够安装三回,如若设置三遍,会抛出十二分。
  • Thread.IsThreadPoolThread,能够看清执行当前代码的线程是或不是为线程池中的线程。

透过这么些办法和品质,有助于我们更好地调节和测试异步调用方法。上边代码中除了加入了部分对线程的操作以外再没有怎么尤其之处。我们建了2个Calculator类,它唯有二个Add方法,我们模拟了那个主意供给实施2分钟时间,并且每隔一秒进行三遍输出。而在客户端程序中,大家运用result变量保存了主意的重返值并拓展了打字与印刷。随后,大家重新模拟了客户端程序接下去的操作须求执行2分钟时间。运营那段程序,会生出下边包车型大巴出口:

Client application started!

Method invoked!
Main Thread: Add executed 1 second(s).
Main Thread: Add executed 2 second(s).
Method complete!
Result: 7

Main Thread: Client executed 1 second(s).
Main Thread: Client executed 2 second(s).
Main Thread: Client executed 3 second(s).

Press any key to exit…

借使你实在执行了那段代码,汇合到这么些输出并不是转眼之间出口的,而是举办了大约5分钟的时日,因为线程是串行执行的,所以在实行完Add()方法之后才会继续客户端剩下的代码。

接下去大家定义多个AddDelegate委托,并运用BeginInvoke()方法来异步地调用它。在上面已经介绍过,BeginInvoke()除了最终四个参数为AsyncCallback类型和Object类型以外,前面包车型大巴参数类型和个数与信托定义相同。其它BeginInvoke()方法再次回到了三个兑现了IAsyncResult接口的指标(实际上正是二个AsyncResult类型实例,注意那里IAsyncResult和AysncResult是差别的,它们均包蕴在.Net
Framework中)。

AsyncResult的用处有这么多少个:传递参数,它包涵了对调用了BeginInvoke()的委托的引用;它还富含了BeginInvoke()的最后一个Object类型的参数;它能够识别出是哪些方法的哪一遍调用,因为经过同多个委托变量可以对同1个艺术调用数十次。

EndInvoke()方法接受IAsyncResult类型的对象(以及ref和out类型参数,那里不探讨了,对它们的处理和再次回到值类似),所以在调用BeginInvoke()之后,我们需求保留IAsyncResult,以便在调用EndInvoke()时开始展览传递。那里最首要的正是EndInvoke()方法的重回值,它正是格局的重回值。除此以外,当客户端调用EndInvoke()时,借使异步调用的法门没有实施达成,则会停顿当前线程而去等待该方法,唯有当异步方法执行实现后才会继续执行前面包车型客车代码。所以在调用完BeginInvoke()后即时执行EndInvoke()是向来不别的意义的。大家家常便饭在尽量早的时候调用BeginInvoke(),然后在要求艺术的再次来到值的时候再去调用EndInvoke(),或许是依照情形在晚些时候调用。说了如此多,大家前日看一下选用异步调用改写后下边包车型地铁代码吧:

public delegate int AddDelegate(int x, int y);

class Program8 {   

    static void Main(string[] args) {

        Console.WriteLine(“Client application started!\n”);
        Thread.CurrentThread.Name = “Main Thread”;
                   
        Calculator cal = new Calculator();
        AddDelegate del = new AddDelegate(cal.Add);
        IAsyncResult asyncResult = del.BeginInvoke(2,5,null,null);  //
异步调用方法

        // 做一点其余的政工,模拟需求履行3分钟
        for (int i = 1; i <= 3; i++) {
            Thread.Sleep(TimeSpan.FromSeconds(i));
            Console.WriteLine(“{0}: Client executed {1} second(s).”,
                Thread.CurrentThread.Name, i);
        }

        int rtn = del.EndInvoke(asyncResult);
        Console.WriteLine(“Result: {0}\n”, rtn);

        Console.WriteLine(“\nPress any key to exit…”);
        Console.ReadKey();
    }
}

public class Calculator { /* 与地方同,略 */}

那儿的出口为:

Client application started!

Method invoked!
Main Thread: Client executed 1 second(s).
Pool Thread: Add executed 1 second(s).
Main Thread: Client executed 2 second(s).
Pool Thread: Add executed 2 second(s).
Method complete!
Main Thread: Client executed 3 second(s).
Result: 7

Press any key to exit…

将来推行完这段代码只需求3分钟时间,四个for循环所发出的出口交替进行,那也印证了那两段代码并行执行的事态。能够看来Add()方法是由线程池中的线程在推行,因为Thread.CurrentThread.IsThreadPoolThread重返了True,同时大家对该线程命名为了Pool
Thread。别的大家得以观望通过EndInvoke()方法获得了重临值。

偶尔,大家可能会将获得重临值的操作放到另一段代码只怕客户端去执行,而不是向下边这样直接写在BeginInvoke()的末尾。比如说大家在Program中新建贰个方法GetReturn(),此时得以经过AsyncResult的AsyncDelegate获得del委托对象,然后再在其上调用EndInvoke()方法,那也印证了AsyncResult能够唯一的获取到与它相关的调用了的主意(只怕也得以精晓成委托对象)。所以地点获得重返值的代码也能够改写成那样:

static int GetReturn(IAsyncResult asyncResult) {
    AsyncResult result = (AsyncResult)asyncResult;
    AddDelegate del = (AddDelegate)result.AsyncDelegate;
    int rtn = del.EndInvoke(asyncResult);
    return rtn;
}

下一场再将int rtn = del.EndInvoke(asyncResult);语句改为int rtn =
GetReturn(asyncResult);。注意上边IAsyncResult要转换为实际的花色AsyncResult才能访问AsyncDelegate属性,因为它从未包罗在IAsyncResult接口的概念中。

BeginInvoke的此外七个参数分别是AsyncCallback和Object类型,当中AsyncCallback是八个委托项目,它用来方法的回调,就是说当异步方法执行完成时自动实行调用的法子。它的定义为:

public delegate void AsyncCallback(IAsyncResult ar);

Object类型用于传递任何你想要的数值,它能够经过IAsyncResult的AsyncState属性获得。下边咱们将取得格局重返值、打字与印刷重返值的操作放到了OnAddComplete()回调方法中:

public delegate int AddDelegate(int x, int y);

class Program9 {

    static void Main(string[] args) {

        Console.WriteLine(“Client application started!\n”);
        Thread.CurrentThread.Name = “Main Thread”;

        Calculator cal = new Calculator();
        AddDelegate del = new AddDelegate(cal.Add);
        string data = “Any data you want to pass.”;
        AsyncCallback callBack = new AsyncCallback(OnAddComplete);
        del.BeginInvoke(2, 5, callBack, data);      // 异步调用方法

        // 做一些别的的事情,模拟必要实施3分钟
        for (int i = 1; i <= 3; i++) {
            Thread.Sleep(TimeSpan.FromSeconds(i));
            Console.WriteLine(“{0}: Client executed {1} second(s).”,
                Thread.CurrentThread.Name, i);
        }

        Console.WriteLine(“\nPress any key to exit…”);
        Console.ReadKey();
    }

    static void OnAddComplete(IAsyncResult asyncResult) {
        AsyncResult result = (AsyncResult)asyncResult;
        AddDelegate del = (AddDelegate)result.AsyncDelegate;
        string data = (string)asyncResult.AsyncState;

        int rtn = del.EndInvoke(asyncResult);
        Console.WriteLine(“{0}: Result, {1}; Data: {2}\n”,
            Thread.CurrentThread.Name, rtn, data);
    }
}
public class Calculator { /* 与地点同,略 */}

它产生的出口为:

Client application started!

Method invoked!
Main Thread: Client executed 1 second(s).
Pool Thread: Add executed 1 second(s).
Main Thread: Client executed 2 second(s).
Pool Thread: Add executed 2 second(s).
Method complete!
Pool Thread: Result, 7; Data: Any data you want to pass.

Main Thread: Client executed 3 second(s).

Press any key to exit…

此处有多少个值得注意的地方:一 、大家在调用BeginInvoke()后不再要求保存IAysncResult了,因为AysncCallback委托将该目的定义在了回调方法的参数列表中;贰 、大家在OnAddComplete()方法中收获了调用BeginInvoke()时最后一个参数字传送递的值,字符串“Any
data you want to pass”;三 、执行回调方法的线程并非客户端线程Main
Thread,而是来自线程池中的线程Pool
Thread。其它如前方所说,在调用EndInvoke()时有可能会抛出特别,所以在应当将它放到try/catch块中,这里笔者就不再示范了。

信托中订阅者方法超时的处理

订阅者除了能够由此充足的不二法门来震慑发表者以外,还是能通过另一种办法:超时。一般说超时,指的是艺术的履行超越某些内定的时刻,而那边小编将含义扩充了一晃,凡是方法执行的日子相比较长,作者就以为它超时了,那些“相比长”是3个比较模糊的定义,2秒、3秒、5秒都足以算得超时。超时和那多少个的区分就是过期并不会潜移默化事件的科学触发和程序的正规运作,却会促成事件触发后需求非常长才能够甘休。在各样执行订阅者的方法那段中间内,客户端程序会被中止,什么也不可能做。因为当执行订阅者方法时(通过委托,也正是各样调用全数注册了的点子),当前线程会转去执行方式中的代码,调用方法的客户端会被中止,唯有当方法执行完结并赶回时,控制权才会回到客户端,从而继续执行下边包车型大巴代码。大家来看一下下边二个事例:

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(“\nControl back to client!”); // 再次回到控制权
    }

    //
触发某些事件,以列表格局再次来到全数办法的再次回到值
    public static object[] FireEvent(Delegate del, params object[] args) {
        // 代码与上同,略
    }
}

public class Publisher {
    public event EventHandler MyEvent;
    public void DoSomething() {
        // 做一些别的的业务
        Console.WriteLine(“DoSomething invoked!”);
        Program6.Fire伊芙nt(My伊夫nt, this, 伊芙ntArgs.Empty); //触发事件
    }
}

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) {
        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, subscriber2 invoked!”);
    }
}

在那段代码中,大家利用Thread.Sleep()静态方法模拟了措施超时的景况。在那之中Subscriber1.On伊芙nt()须求三分钟实现,Subscriber2.On伊夫nt()立时实施,Subscriber3.On伊芙nt必要两秒达成。那段代码完全能够符合规律输出,也从不分外抛出(若是有,也唯有是该订阅者被忽略掉),下边是出口的动静:

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()时,此委托对象只好分包2个目的措施,所以对于三个订阅者注册的动静,必须接纳GetInvocationList()得到全部寄托对象,然后遍历它们,分别在其上调用BeginInvoke()方法。假如平昔在信托上调用BeginInvoke(),会抛出至极,提醒“委托只好分包1个指标措施”。
  • 固然订阅者的方法抛出十分,.NET会捕捉到它,可是唯有在调用EndInvoke()的时候,才会将万分重新抛出。而在本例中,大家不应用EndInvoke()(因为我们不尊敬订阅者的实施情状),所以大家无需处理卓殊,因为正是抛出万分,也是在另1个线程上,不会潜移默化到客户端线程(客户端照旧不明白订阅者爆发了相当,那有时是好事有时是帮倒忙)。
  • BeginInvoke()方法属于委托定义所生成的类,它既不属于MulticastDelegate也不属于Delegate基类,所以无法持续运用可采纳的Fire伊夫nt()方法,大家须求开始展览3个向下更换,来博取到骨子里的寄托项目。

当今大家修改一下方面包车型客车顺序,使用异步调用来化解订阅者方法执行超时的事态:

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(提姆eSpan.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!”);
    }
}

运作方面包车型客车代码,会获得上面包车型大巴输出:

DoSomething invoked!
Control back to client!

Press any thing to exit…

Waited for 2 seconds, subscriber3
invoked!
Waited for 3 seconds, subscriber1
invoked!

须要留意代码输出中的多少个变化:

  1. 我们须要在客户端程序中调用Console.ReadKey()方法来刹车客户端,以提供丰盛的命宫来让异步方法去实践完代码,不然的话客户端的程序到这里便会运行截至,程序会退出,不会见到其余订阅者方法的输出,因为它们根本没赶趟执行完毕。原因是那般的:客户端所在的线程我们常见称为主线程,而推行订阅者方法的线程来自线程池,属于后台线程(Background
    Thread),当主线程停止时,不论后台线程有没有收尾,都会退出程序。(当然还有一种前台线程(Foreground
    Thread),主线程截至后务必等前台线程也终结后先后才会脱离,关于线程的议论能够开辟另多少个宏大的宗旨,那里就不商量了)。
  2. 在打字与印刷完“Press any thing to
    exit…”之后,五个订阅者的方法会以2秒、1秒的间距突显出来,且就算大家先注册了subscirber1,但是却先实行了subscriber3,这是因为执行它必要的时间更短。除此以外,注意到这八个艺术是并行执行的,所以进行它们的总时间是最长的办法所要求的日子,也正是3秒,而不是她们的累加5秒。
  3. 就如前边所关联的,就算subscriber2抛出了11分,大家也没有指向非凡实行拍卖,可是客户程序并从未察觉到,程序也未尝就此而暂停。

总结

那篇小说是对自个儿事先写的C#中的委托和事件的二个补偿,大致分成了多个部分,第贰片段讲述了几个容易让人发出猜疑的难点:为何选取事件而不是委托变量,为何日常委托的定义都回去void;第一局地讲述了怎样处理10分和过期;第一片段则讲述了通过信托完结异步方法的调用。

多谢阅读,希望那篇文章能给您带来扶助。

 

信托和章程的异步调用

一般性状态下,假使须要异步执行3个耗费时间的操作,大家会新起三个线程,然后让那些线程去实施代码。不过对于每1个异步调用都经过创办线程来展开操作显明会对品质发生一定的震慑,同时操作也针锋相对繁琐一些。.Net中得以透过委托举行艺术的异步调用,就是说客户端在异步调用方法时,自身并不会因为方法的调用而有始无终,而是从线程池中抓取2个线程去执行该办法,自个儿线程(主线程)在完结抓取线程这一经过之后,继续执行下边包车型大巴代码,这样就达成了代码的并行执行。使用线程池的便宜正是制止了反复举办异步调用时创建、销毁线程的支付。

犹如上边所示,当大家在委托对象上调用BeginInvoke()时,便展开了2个异步的格局调用。地点的例子中是在事变的宣布和订阅这一进程中使用了异步调用,而在事变发表者和订阅者之间频仍是松耦合的,公布者平日不供给获得订阅者方法执行的情事;而当使用异步调用时,越多景况下是为着提高系统的属性,而毫无专用于事件的发表和订阅这一编制程序模型。而在那种情景下使用异步编制程序时,就需求实行更加多的决定,比如当异步执行措施的艺术截至时通报客户端、再次来到异步执行格局的重临值等。本节就对BeginInvoke()方法、EndInvoke()方法和其有关的IAysncResult做三个粗略的牵线。

NOTE:专注此处小编已经不再使用公布者、订阅者这一个术语,因为我们不再是探究下面的轩然大波模型,而是商讨在客户端程序中异步地调用方法,那里有三个想想的转移。

我们看这么一段代码,它以身作则了不利用异步调用的平常状态:

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

        Console.WriteLine(“Client application started!\n”);
        Thread.CurrentThread.Name =
“Main Thread”;

        Calculator cal = new Calculator();
        int result = cal.Add(2, 5);
        Console.WriteLine(“Result: {0}\n”, result);
       
        //
做一些其余的政工,模拟须求执行3分钟
        for (int i = 1; i <= 3; i++) {
            Thread.Sleep(TimeSpan.FromSeconds(i));
            Console.WriteLine(“{0}: Client executed {1} second(s).”,
                Thread.CurrentThread.Name, i); 
        }

        Console.WriteLine(“\nPress any key to exit…”);
        Console.ReadKey();
    }
}

public class Calculator {
    public int Add(int
x, int y) {
        if (Thread.CurrentThread.IsThreadPoolThread)
{
            Thread.CurrentThread.Name = “Pool Thread”;
        }
        Console.WriteLine(“Method invoked!”);          

        //
执行有个别事情,模拟须要履行2分钟
        for (int i = 1; i <= 2; i++) {
            Thread.Sleep(TimeSpan.FromSeconds(i));
            Console.WriteLine(“{0}: Add executed {1} second(s).”,
                Thread.CurrentThread.Name, i); 
        }
        Console.WriteLine(“Method complete!”);
        return x + y;
    }
}

地方代码有多少个有关对于线程的操作,借使不打听能够看一下底下的印证,如若您早就精通能够直接跳过:

  • Thread.Sleep(),它会让实施当前代码的线程暂停一段时间(假如你对线程的概念相比面生,可以驾驭为使程序的实施暂停一段时间),以微秒为单位,比如Thread.Sleep(1000),将会使线程暂停1秒钟。在上头我动用了它的重载方法,个人认为使用TimeSpan.FromSeconds(1),可读性更好一些。
  • Thread.CurrentThread.Name,通过那特特性可以安装、获取执行当前代码的线程的称号,值得注意的是那一个性格只好够安装二遍,假设设置一回,会抛出万分。
  • Thread.IsThreadPoolThread,能够判定执行当前代码的线程是不是为线程池中的线程。

经过那多少个措施和性质,有助于我们更好地调节和测试异步调用方法。上边代码中除了参与了一部分对线程的操作以外再没有什么越发之处。大家建了3个Calculator类,它唯有贰个Add方法,大家模拟了那些方法需求执行2分钟时间,并且每隔一秒举办三回输出。而在客户端程序中,大家利用result变量保存了法子的再次来到值并拓展了打字与印刷。随后,大家再次模拟了客户端程序接下去的操作须要履行2分钟时间。运维那段程序,会发出下边包车型地铁输出:

Client application started!

Method invoked!
Main Thread: Add executed 1
second(s).
Main Thread: Add executed 2
second(s).
Method complete!
Result: 7

Main Thread: Client executed 1
second(s).
Main Thread: Client executed 2
second(s).
Main Thread: Client executed 3
second(s).

Press any key to exit…

只要你真正执行了那段代码,会面到这个输出并不是一弹指顷出口的,而是实行了差不多5分钟的时间,因为线程是串行执行的,所以在进行完Add()方法之后才会持续客户端剩下的代码。

接下去我们定义2个AddDelegate委托,并运用BeginInvoke()方法来异步地调用它。在下边已经介绍过,BeginInvoke()除了尾数参数为AsyncCallback类型和Object类型以外,前边的参数类型和个数与寄托定义相同。其它BeginInvoke()方法重返了2个兑现了IAsyncResult接口的目的(实际上便是1个AsyncResult类型实例,注意那里IAsyncResult和AysncResult是例外的,它们均包括在.Net
Framework中)。

AsyncResult的用处有诸如此类多少个:传递参数,它涵盖了对调用了BeginInvoke()的寄托的引用;它还含有了BeginInvoke()的最终三个Object类型的参数;它能够分辨出是哪些方法的哪一次调用,因为通过同一个委托变量可以对同三个措施调用数十次。

EndInvoke()方法接受IAsyncResult类型的对象(以及ref和out类型参数,那里不斟酌了,对它们的拍卖和重临值类似),所以在调用BeginInvoke()之后,大家要求保留IAsyncResult,以便在调用EndInvoke()时实行传递。那里最注重的就是EndInvoke()方法的重回值,它正是方法的重返值。除此以外,当客户端调用EndInvoke()时,假设异步调用的措施没有执行完成,则会半上落下当前线程而去等待该形式,唯有当异步方法执行完毕后才会继续执行前边的代码。所以在调用完BeginInvoke()后立刻执行EndInvoke()是素来不别的意义的。大家一般在尽量早的时候调用BeginInvoke(),然后在须要艺术的再次回到值的时候再去调用EndInvoke(),大概是基于气象在晚些时候调用。说了如此多,大家以后看一下采纳异步调用改写后上边包车型大巴代码吧:

public delegate int
AddDelegate(int x, int y);

class Program8 {   

    static void Main(string[] args) {

        Console.WriteLine(“Client application started!\n”);
        Thread.CurrentThread.Name =
“Main Thread”;
                   
        Calculator cal = new Calculator();
        AddDelegate del = new AddDelegate(cal.Add);
        IAsyncResult asyncResult =
del.BeginInvoke(2,5,null,null);  //
异步调用方法

        //
做一点别的的事体,模拟必要进行3分钟
        for (int i = 1; i <= 3; i++) {
            Thread.Sleep(TimeSpan.FromSeconds(i));
            Console.WriteLine(“{0}: Client executed {1} second(s).”,
                Thread.CurrentThread.Name, i);
        }

        int rtn =
del.EndInvoke(asyncResult);
        Console.WriteLine(“Result: {0}\n”, rtn);

        Console.WriteLine(“\nPress any key to exit…”);
        Console.ReadKey();
    }
}

public class Calculator { /* 与地点同,略 */}

此刻的出口为:

Client application started!

Method invoked!
Main Thread: Client executed 1
second(s).
Pool Thread: Add executed 1
second(s).
Main Thread: Client executed 2
second(s).
Pool Thread: Add executed 2
second(s).
Method complete!
Main Thread: Client executed 3
second(s).
Result: 7

Press any key to exit…

目前推行完这段代码只必要3秒钟时间,多个for循环所发生的出口交替进行,这也表达了那两段代码并行执行的事态。能够看看Add()方法是由线程池中的线程在举办,因为Thread.CurrentThread.IsThreadPoolThread再次回到了True,同时大家对该线程命名为了Pool
Thread。其余我们得以观望通过EndInvoke()方法获得了再次来到值。

有时候,大家恐怕会将获取重返值的操作放到另一段代码也许客户端去履行,而不是向上边那样直接写在BeginInvoke()的末端。比如说大家在Program中新建一个主意GetReturn(),此时可以透过AsyncResult的AsyncDelegate拿到del委托对象,然后再在其上调用EndInvoke()方法,这也证实了AsyncResult能够唯一的拿走到与它相关的调用了的措施(可能也能够领略成委托对象)。所以地点获得重返值的代码也得以改写成那样:

static int GetReturn(IAsyncResult asyncResult) {
    AsyncResult result =
(AsyncResult)asyncResult;
    AddDelegate del =
(AddDelegate)result.AsyncDelegate;
    int rtn =
del.EndInvoke(asyncResult);
    return rtn;
}

接下来再将int rtn = del.EndInvoke(asyncResult);语句改为int rtn =
GetReturn(asyncResult);。注意上面IAsyncResult要更换为实际的类别AsyncResult才能访问AsyncDelegate属性,因为它没有包蕴在IAsyncResult接口的概念中。

BeginInvoke的其它八个参数分别是AsyncCallback和Object类型,其中AsyncCallback是叁个信托项目,它用来方法的回调,正是说当异步方法执行完结时自动举办调用的法门。它的概念为:

public delegate void AsyncCallback(IAsyncResult ar);

Object类型用于传递任何你想要的数值,它能够透过IAsyncResult的AsyncState属性得到。上边大家将得到方式再次来到值、打字与印刷重返值的操作放到了OnAddComplete()回调方法中:

public delegate int
AddDelegate(int x, int y);

class Program9 {

    static void Main(string[] args) {

        Console.WriteLine(“Client application started!\n”);
        Thread.CurrentThread.Name =
“Main Thread”;

        Calculator cal = new Calculator();
        AddDelegate del = new AddDelegate(cal.Add);
        string data = “Any data you want to pass.”;
        AsyncCallback callBack =
new AsyncCallback(OnAddComplete);
        del.BeginInvoke(2, 5, callBack, data);      // 异步调用方法

        //
做一些别的的业务,模拟必要执行3分钟
        for (int i = 1; i <= 3; i++) {
            Thread.Sleep(TimeSpan.FromSeconds(i));
            Console.WriteLine(“{0}: Client executed {1} second(s).”,
                Thread.CurrentThread.Name, i);
        }

        Console.WriteLine(“\nPress any key to exit…”);
        Console.ReadKey();
    }

    static void OnAddComplete(IAsyncResult asyncResult)
{
        AsyncResult result =
(AsyncResult)asyncResult;
        AddDelegate del =
(AddDelegate)result.AsyncDelegate;
        string data = (string)asyncResult.AsyncState;

        int rtn =
del.EndInvoke(asyncResult);
        Console.WriteLine(“{0}: Result, {1}; Data: {2}\n”,
            Thread.CurrentThread.Name, rtn, data);
    }
}
public class Calculator { /* 与地点同,略 */}

它发出的输出为:

Client application started!

Method invoked!
Main Thread: Client executed 1
second(s).
Pool Thread: Add executed 1
second(s).
Main Thread: Client executed 2
second(s).
Pool Thread: Add executed 2
second(s).
Method complete!
Pool Thread: Result, 7; Data: Any
data you want to pass.

Main Thread: Client executed 3
second(s).

Press any key to exit…

此地有多少个值得注意的地点:一 、我们在调用BeginInvoke()后不复需求保存IAysncResult了,因为AysncCallback委托将该对象定义在了回调方法的参数列表中;二 、大家在OnAddComplete()方法中得到了调用BeginInvoke()时最终2个参数字传送递的值,字符串“Any
data you want to pass”;三 、执行回调方法的线程并非客户端线程Main
Thread,而是源于线程池中的线程Pool
Thread。其它如前方所说,在调用EndInvoke()时有大概会抛出特别,所以在相应将它内置try/catch块中,那里本身就不再示范了。

总结

那篇文章是对作者在此之前写的C#中的委托和事件的一个补偿,大致分成了四个部分,第2部分讲述了多少个简单令人爆发质疑的难点:为啥使用事件而不是委托变量,为啥日常委托的概念都回去void;第③局地讲述了什么处理万分和过期;第一片段则描述了通过委托完毕异步方法的调用。

感激阅读,希望那篇小说能给您带来援救。

转自:http://www.tracefact.net/CSharp-Programming/Delegates-and-Events-Advanced.aspx