聚拓互联

http://www.ejutuo.com
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

 

<五> 委托与事件

 

1、委托

   C#的委托相当于C/C++中的函数指针,函数指针用指针获取一个函数的入口地址,实现对函数的操作。委托是面向对象的,是引用类型,因此对委托的使用要先定义后实例化,最后才调用。委托与C/C++有本质的区别:C/C++中的函数指针仅仅是一个内存地址,其中不包含任何有关函数的参数、返回值以及调用约定等方面的信息。

 

2、声明委托

   C#使用关键字delegate声明委托类型。格式如下:

   [修饰符] delegate 结果类型 委托名称([形式参数列表]);

   ◆ 访问修饰符为可选项,可以使用new、public、private、protected和internal之一。

   ◆ 结果类型指委托对应方法的返回值的类型。

   ◆ 形式参数列表指定委托对应方法的参数。注意,如果参数是一个指针,则必须用unsafe修饰符声明委托。

 

3、委托的实现

   在C#中,可以通过以下三个步骤定义和使用委托:声明、建立委托实例、实例化和调用。上面已经介绍了如何声明一个委托,下面介绍后两个步骤,即建立委托实例、实例化和调用。

   delegate语句定义委托的参数类型(形式参数列表的参数类型)和返回值类型(结果类型),任何具有匹配参数类型和返回值类型的方法均可用来建立此委托的实例。因此,声明了一个委托后,为了实现这个委托,可以按如下的步骤操作:

 (1)建立委托实例:委托实例是委托所要调用的方法,它与声明的委托在以下三个方面必须相同。

◆ 方法类型。如果委托被声明的为void(无返回值)结果类型,则委托实例(方法)必须是void类型;如果委托声明的是其它结果类型(如int类型),则委托实例(方法)返回值类型必须与之对应。

      ◆ 参数类型。委托声明的参数列表类型必须与委托实例(方法)的参数列表类型相同。

      ◆ 参数个数。委托声明的参数列表个数必须与委托实例(方法)的参数列表个数相同。

   除上述三个方面外,其他方面,例如实例与委托的名称、参数名称不必相同。只要这三个方面相同,就可以作为一个委托的实例过程。

 (2)实例化和调用:类名 类的对象名=new 类名();

                   委托名 委托的对象名=new 委托名(类的对象名.方法名)

               委托的对象名=类的对象名.方法名

 

   下面通过一个实例说明如何使用委托

using System;

using System.Collections;

namespace 笔记

{

   public class SimpleClass

    {

        public class WorkerClass

        {

            //建立委托实例:非静态方法

            public int InstanceMethod(int nID, string sName)

            {

                int retval = 0;

                retval = nID * sName.Length;

                Console.WriteLine("调用InstanceMethod方法");

                return retval;

            }

 

            //建立委托实例:静态方法

            static public int StaticMethod(int nID, string sName)

            {

                int retval = 0;

                retval = nID * sName.Length;

                Console.WriteLine("调用StaticMethod方法");

                return retval;

            }

        }

 

        //定义委托,返回类型、参数与上面两个方法相同

       public delegate int SomeDelegate(int nID, string sName);

 

       public static void MMain()

       {

           //调用委托实例:非静态方法

           WorkerClass wr = new WorkerClass();

           SomeDelegate d1=new SomeDelegate(wr.InstanceMethod);

           Console.WriteLine("Invoking delegate InstanceMethod,return={0}",d1(5,"aaa"));//红色的为调用委托

 

           //调用委托实例:静态方法

           SomeDelegate d2 = new SomeDelegate(WorkerClass.StaticMethod);

           Console.WriteLine("Invoking delegate StaticMethod,return={0}", d2(5, "bbb"));

       }

    }

}

 

程序运行结果:

调用InstanceMethod方法

Invoking delegate InstanceMethod,return=15

调用StaticMethod方法

Invoking delegate StaticMethod,return=15

   从上面程序可以看出,使用委托,首先要用new关键字创建一个委托对象,同时为委托对象指明引用的方法名称,然后就可在程序中像使用方法名调用方法一样用委托对象引用方法。

 

4、多播

   相对于上面的一次委托只调用一个方法,一次委托也可以调用多个方法,称为多播。通过“+”和“-”运算符实现多播的增加或减少。

   多播示例。

using System;

using System.Collections;

namespace 笔记

{

   public class SimpleClass

    {

        public class WorkerClass

        {

            //建立委托实例:非静态方法

            public int InstanceMethod(int nID, string sName)

            {

                int retval = 0;

                retval = nID * sName.Length;

                Console.WriteLine("调用InstanceMethod方法");

                return retval;

            }

 

            //建立委托实例:静态方法

            static public int StaticMethod(int nID, string sName)

            {

                int retval = 0;

                retval = nID * sName.Length;

                Console.WriteLine("调用StaticMethod方法");

                return retval;

            }

        }

 

       //定义委托,返回类型、参数与上面两个方法相同

       public delegate int SomeDelegate(int nID, string sName);

       public static void MMain()

       {

           //调用委托实例:非静态方法

           WorkerClass wr = new WorkerClass();

           SomeDelegate d1=new SomeDelegate(wr.InstanceMethod);

           Console.WriteLine("Invoking delegate InstanceMethod,return={0}",d1(5,"aaa"));

 

           //调用委托实例:静态方法

           SomeDelegate d2 = new SomeDelegate(WorkerClass.StaticMethod);

           Console.WriteLine("Invoking delegate StaticMethod,return={0}", d2(5, "bbb"));

 

           //多播

           Console.WriteLine();

           Console.WriteLine("---------测试多播----------");

 

           //多播d3由两个委托d1和d2组成

           SomeDelegate d3 = d1 + d2;

           Console.WriteLine("多播d3由两个委托d1和d2组成:{0}", d3(5, "ccc"));

 

           //委托中的方法个数

           int Num_Method = d3.GetInvocationList().Length;

           Console.WriteLine("多播委托d3中的方法个数:{0}", Num_Method);

 

           //多播d3减去委托d2

           d3 = d3 - d2;

           Console.WriteLine("多播d3减去委托d2:{0}", d3(5, "ddd"));

 

           //委托中的方法个数

           Num_Method = d3.GetInvocationList().Length;

           Console.WriteLine("多播委托d3中的方法个数:{0}", Num_Method);

       }

    }

}

程序运行结果:

调用InstanceMethod方法

Invoking delegate InstanceMethod,return=15

调用StaticMethod方法

Invoking delegate StaticMethod,return=15

 

---------测试多播----------

调用InstanceMethod方法

调用StaticMethod方法

多播d3由两个委托d1和d2组成:15

多播委托d3中的方法个数:2

调用InstanceMethod方法

多播d3减去委托d2:15

多播委托d3中的方法个数:1

   多播开始的时候,d3中有两个委托d1和d2,d3(5,“ccc”)调用等于后一个d2(5,“ccc”)。从d3中减去d2,d3中只有一个方法d1,d3(5,“ddd”)调用等d1(5,“aaa”)。D3.GetInvocationList().Length表示的是多播实例中委托包含的方法个数。委托是从System.Delegate类派生而来,多播则派生于System.Delegate的派生类System.MulticastDelegate。对于下面的代码:

   SomeDelegate d3=d1+d2;

   也可以用Delegate.Combine方法写成:

   SomeDelegate d3=(SomeDelegate) Delegate.Combine(d1,d2);

   也可以使用MulticastDelegate.Combine的方法写成:

   SomeDelegate d3=(SomeDelegate)MulticastDelegate.Combine(d1,d2);

说明:

Combine方法。将指定的可组合多播委托列表连接起来。

Remove方法。从一个委托的调用列表中移除另一个委托的调用列表。例如:

   //合并

   MyDelegate newDelegate=MyDelegate.Combine(delegate1,delegate2);

   //删除

   MyDelegate newDelegate=MyDelegate.Remove(delegate1,delegate2);

 

5、事件

   事件作为C#中的一种类型,为类和类的实例定义发出通知的能力,从而将事件和可执行代码捆绑在了一起。事件最常见的用途是用于窗体编程,当发生像点击按钮、移动鼠标等事件时,相应的程序将收到通知,再执行代码。

   C#事件是按“发布-预订”的方式工作。先在一个类中公布事件,然后就可以在任意数量的类中对事件预订。事件的工作过程可以用下图表示。

 

事件TextOut

事件TextOut的委EventHandler

处理事件的方法CatchEvent

处理事件的方法InstanceCatch

   C#事件机制是基于委托实现的,因此要首先定义一个委托EventHandler:

   Public delegate void EventHandler(object from,myEventArgs e)

   它有两个参数,第一个参数为发送者,第二个参数为事件参数,是从.NET构架的EventArgs类派生的。

   在.NET框架中,所有的事件处理都具有这种模式。由第一个参数指明触发事件的对象,由第二个参数包含事件信息,而且没有返回值。对于用户自定义事件处理,则可以使用任意类型的委托类型。

 

定义事件格式:

[修饰符] event事件的委托名 事件名;

[修饰符] event 事件的委托名 {访问函数声明代码};

可选修饰符包括:abstract,new,override,static,virtual五个访问修饰符之一。不能既包含abstract修饰符又包含事件访问函数声明。包含extern修饰符时,就成为外部事件。因为外部事件声明不提供任何实际的实现代码,所以也不能既包含extern修饰符又包含事件访问函数声明。

访问函数声明代码用于添加或移除客户代码中的事件处理程序。访问方法为add和remove,两者必须同时定义。

事件声明的类型必须是委托类型。

创建事件的一般步骤如下:

 (1)创建一个类(提供数据类):它包含“处理事件类”可以使用的数据(即是向“处理事件类”中的方法提供数据)。该类继承EventArgs类。如:

    //定义MyEventArgs类——提供数据类:该类包含“处理事件类”可以使用的数据

    public class MyEventArgs : EventArgs

    {

        private string StrText;

        public MyEventArgs(string Str)

        {

            this.StrText = Str;

        }

        public string GetStrText

        {

            get { return StrText; }

        }

    }

   (2) 创建一个类(引发事件类):该类用来引发事件。在这个类中要做下如下工作:

   ◆ 声明委托:public delegate void EventHandler(object from, MyEventArgs e)

      其中,EventHandler是委托名称,它有两个参数object from和MyEventArgs e。object from表示引发事件的源;MyEventArgs e表示一个类,它包“处理事件类”可以使用的数据,这个类由EventArgs类派生来。

   ◆ 声明事件:[修饰符] event事件的委托名 事件名。

   ◆ 写方法:该方法用来激活事件的。(即时编写激活事件的方法)如:

    //定义EventSource类——引发事件类:该类的主要作用是用来引发事件

    class EventSource

{

     //MyEventArgs是一个类,包含事件处理程序可以使用的数据

        MyEventArgs Evargs = new MyEventArgs("触发事件");

 

        public delegate void EventHandler(object from, MyEventArgs e);    //定义委托

 

        public event EventHandler TextOut;                               //定义事件

 

        //激活事件的方法

        public void TriggerEvent()

        {

            if (TextOut != null)

                TextOut(this,Evargs);

        }

    }

 

 (3)创建一个类(处理事件类):该类包含处理事件的一个或多个方法。如:

    //定义TestApp类——处理事件类:该类包含处理事件的方法CatchEvent和InstanceCatch

    public class TestApp

    {

        //处理事件的静态方法

        public static void CatchEvent(object from, MyEventArgs e)

        {

            Console.WriteLine("CatchcEvent:{0}", e.GetStrText);

        }

        //处理事件的方法

        public void InstanceCatch(object form, MyEventArgs e)

        {

            Console.WriteLine("InstanceCatch:{0}", e.GetStrText);

        }

     }

 (4)创建一个类(执行类):必须将事件处理程序和事件关联起来,以便事件发生时执行该处理程序。要实现关联,必须首先包含事件的对象,形式为:

   包含事件的类名 对象名=new 包含事件的类名();

创建对象之后就可使用事件,每当包含事件的类的对象的set被调用时,就会创建对象和引发对象。声明一个包含事件的对象之后,使用“+=”运算符可关联事件处理程序和该对象,形式为:

ObjectWithEventName.EvenObj+=new EventDelegate(EventName);

其中,ObjectWithEventName为使用事件类声明的对象;EventObj为事件名称;“+=”为一个指示器,用于指出接下来要将一个事件处理程序加入到事件中;new指出应创建接下来的事件处理程序;EventDelegateName为事件处理程序的名称。如:

    //定义DiaoYong类:执行“处理事件的方法CatchEvent和InstanceCatch”

    public class DiaoYong

    {

        public static void MMain()

        {

            EventSource evsrc = new EventSource();              //实例化“引发事件类”

            TestApp theApp = new TestApp();                     //实例化“处理事件类”

            evsrc.TextOut += new EventSource.EventHandler(theApp.InstanceCatch);   //绑定InstanceCatch方法到事件

            evsrc.TriggerEvent();     //触发事件

            evsrc.TextOut -= new EventSource.EventHandler(theApp.InstanceCatch);   //取消事件与处理事件方法的关联

            evsrc.TextOut += new EventSource.EventHandler(TestApp.CatchEvent);     //绑定CatchEvent方法到事件

            evsrc.TriggerEvent();     //触发事件

        }

    }

 

下面通过一个实例说明如何创建事件并使用事件

using System;

using System.Collections;

namespace 笔记

{

    //定义MyEventArgs类——提供数据类:该类包含“处理事件类”可以使用的数据

    public class MyEventArgs : EventArgs

    {

        private string StrText;

        public MyEventArgs(string Str)

        {

            this.StrText = Str;

        }

        public string GetStrText

        {

            get { return StrText; }

        }

    }

 

    //定义EventSource类——引发事件类:该类的主要作用是用来引发事件

    class EventSource

{

//MyEventArgs是一个类,包含事件处理程序可以使用的数据

        MyEventArgs Evargs = new MyEventArgs("触发事件");

 

        //定义委托

        public delegate void EventHandler(object from, MyEventArgs e);    //定义委托

 

        public event EventHandler TextOut;                                 //定义事件

 

        //激活事件的方法

        public void TriggerEvent()

        {

            if (TextOut != null)

                TextOut(this,Evargs);

        }

    }

 

 

    //定义TestApp类——事件处理类:该类包含事件处理的方法CatchEvent和InstanceCatch

    public class TestApp

    {

        //处理事件的静态方法

        public static void CatchEvent(object from, MyEventArgs e)

        {

            Console.WriteLine("CatchcEvent:{0}", e.GetStrText);

        }

        //处理事件的方法

        public void InstanceCatch(object form, MyEventArgs e)

        {

            Console.WriteLine("InstanceCatch:{0}", e.GetStrText);

        }

    }

 

    //定义DiaoYong类:执行“处理事件的方法CatchEvent和InstanceCatch”

    public class DiaoYong

    {

        public static void MMain()

        {

            EventSource evsrc = new EventSource();              //实例化“引发事件类”

            TestApp theApp = new TestApp();                     //实例化“处理事件类”

            evsrc.TextOut += new EventSource.EventHandler(theApp.InstanceCatch);   //绑定InstanceCatch方法到事件

            evsrc.TriggerEvent();     //触发事件

            evsrc.TextOut -= new EventSource.EventHandler(theApp.InstanceCatch);   //取消事件与处理事件方法的关联

            evsrc.TextOut += new EventSource.EventHandler(TestApp.CatchEvent);     //绑定CatchEvent方法到事件

            evsrc.TriggerEvent();     //触发事件

        }

    }

}

 

ClassD说:ClassB类触发ClassA类的方法时,需要给方法提供数据,那么提供数据就由我现实。就叫我提供数据类吧!

需要ClassC做关联

需要ClassC做关联

ClassA说:我是一包含方法类,里面有两个方法,我需要一个事件触发它们,让这两个方法运行起来。

ClassC说:我是一个将“事件类”与“包含方法类”关联起来的类。就叫我事件方法关联类吧!

ClassB说:我是一个事件类,类中定义了:(1一个委托     2一个事件;3一个方法。该方法是激活事件的,激活事件后就能执行“包含方法类”的中方法了。

需要ClassB触发

提供数据给ClassB

 

6、使用动态调用方法

输入两个数,再选择运算方法,程序可算出结果。

首先声明了一个委托类型Calculate

public delegate double Calculate(double x, double y);

    然后在窗体中定义了一个私有字段curOpt,表明当前选中的操作类型。注意,它是一个Calculate委托类型的变量。

     private Calculate curOpt;

    以下函数为整个程序的核心:

        void DoCalulate(Calculate calmethod)

        {

            double x, y;

            try

            {

                x = Convert.ToDouble("数值1");

                y = Convert.ToDouble("数值2");

               calmethod(x, y);

            }

            catch

           {

                throw new Exception();

            }

        }

 

    请注意函数的参数是Calculate委托类型,通过委托变量动态地调用“加”,“减”,“乘”和“除”4个方法之一。

    以减法为例,首先需要定义一个完成减法操作的函数:

        double Subtract(double x, double y)

        {

            return x - y;

        }

    在表示减法选项的单选钮rdoSubtract单击事件中将Subtract()函数赋予委托变量curOpt,再作为DoCalculate()方法的实参传入。

        private void radoSubtract_CheckedChanged(object sender, EventArgs e)

        {

            curOpt = this.Subtract;     //选择函数

            DoCalulate(curOpt);         //调用函数

          //--------------------------- 等价于

           DoCalulate(new Calculate(Subtract));

        }

    这一示例的关键是将委托作为函数DoCalculate()的参数,从而实现了在程序运行时动态选择要执行的函数,避免了在函数DoCalculate中采用条件语句进行判断。

 

7、利用委托实现回调

    利用接口可以实现回调,事实上,实现回调的最直观的方法是使用委托。

    下面程序回调两个函数显示当前时间。

    首先定义一个委托,它决定要被回调的函数签名。

    public delegate void ShowTime();

    接着,定义两个要被回调的类:

    class A

    {

        public void AShowTime()

        {

            System.Console.WriteLine("A:" + DateTime.Now);

        }

    }

    class B

    {

        public static void BShowTime()

        {

            System.Console.WriteLine("B:" + DateTime.Now);

        }

    }

    类A提供了一个动态方法AShowTime(),类B提供了一个静态方法BShowTime(),可以看到它们都符合委托ShowTime的要求。

    类Controller完成回调的核心工作。

    class Controller

    {

        private ShowTime d;     //用于接收外界对象提供的方法,以实现回调

        //外界对象将需要回调的方法传入.

        public void RegisterDelegateForCallback(ShowTime method)

        {

            d += method;

        }

        //移除要回调的方法

        public void UnRegisterDelegateForCallBack(ShowTime method)

        {

            d -= method;

        }

        //实现回调

        public void CallBack()

        {

            if (d != null)

            {

                d.Invoke();     //调用所有回调的方法

            }

        }

    }

    类Controller用一个ShowTime类型的私有字段d存储回调方法列表,调用RegisterDelegateForCallBack()方法向回调方法列表中增加回调方法,调用UnRegisterDelegateForCallBack()方法回调方法列表中移除回调方法。

    class Program

    {

        static void Main(string[] args)

        {

            A a = new A();

            Controller c = new Controller();

            //注册两个回调方法

            c.RegisterDelegateForCallback(a.AShowTime);

            c.RegisterDelegateForCallback(B.BShowTime);

            Console.WriteLine("敲任意键显示当前时间,ESC键退出...");

            while (Console.ReadKey(true) != ConsoleKey.Escape)

            {

                c.CallBack();   //回调.

            }

        }

    }

 

8、异步编程

    在许多程序中,代码是顺序执行的,如果在代码中调用了一个函数,则必须等待函数的所有代码执行完毕之后,才能回到原来地方执行下一行代码。这种程序运行方式称为“同步”。

8.1 同步编程方式

    以下实例说明同步的含义:这个程序在运行时要求用户输入一个文件夹名字,然后计算这个文件夹的容量。

    静态方法CalculateFolderSize()负责计算出指定文件夹(包括子文件夹)的总容量。

        private static long CalculateFloderSize(string folderName)

        {

            if (Directory.Exists(folderName) == false)

            {

                throw new DirectoryNotFoundException("文件夹不存在");

            }

            DirectoryInfo rootDir = new DirectoryInfo(folderName);

            //获取所有的子文件夹

            DirectoryInfo[] childDirs = rootDir.GetDirectories();

            //获取当前文件夹中的所有文件

            FileInfo[] files = rootDir.GetFiles();

            long totalSize = 0;

            //累加每个文件的大小

            foreach (FileInfo file in files)

            {

                totalSize += file.Length;

            }

            //对每个文件夹执行同样的计算过程:累加其下每个文件的大小

            //这是通过递归调用来实现的

            foreach (DirectoryInfo dir in childDirs)

            {

                totalSize += CalculateFloderSize(dir.FullName);

            }

            //返回文件夹的总容量

            return totalSize;

        }

    需要注意的是,CalculateFolderSize()方法采用递归方法计算文件夹容量。

    主程序很简单,如下所示:

        static void Main(string[] args)

        {

            long size;

            string folderName;

            Console.WriteLine("请输入文件夹名称,如:C:\\Windows");

            folderName = Console.ReadLine();

            size = CalculateFloderSize(folderName);

            Console.WriteLine("\n文件夹{0}的容量为:{1}字节\n", folderName, size);

       }

    程序运行结果如下:

请注意第7句调用了函数CalculateFolderSize(),如果此函数不返回,则第8句是不可能执行的。

    如果函数CalculateFolderSize()执行需要一定的时间(对于层次很深,文件很多的文件夹,比如Windows系统文件所在的文件夹,函数的运行时间还很长),则用户在函数返回前看不到任何信息,他有可能会以为计算机死机了。

    上述程序就是典型的程序同步执行模式。

    能不能在调用函数之后,不用等待函数执行完成就马上执行下一条语句呢?要实现这个功能,就必须采用“异步编程模式”

 

 

 

 

 

8.2 异步编程方式

    让我们把上述改造为异步编程模式,当函数正在计算时给用户一个提示信息,要求他耐心等待.

    首先定义一个委托。

        public delegate long CalculateFolderSizeDelegate(string folderName);

 

Main()函数中的代码如下:

        static void Main(string[] args)

        {

            //定义一个委托变量并引用静态方法CalculateFolderSize

            CalculateFolderSizeDelegate d = new CalculateFolderSizeDelegate(CalculateFloderSize);

            Console.WriteLine("请输入文件夹名称,如C:\\windows");

            string floderName = Console.ReadLine();

            //通过委托异步调用静态方法CalculateFolderSize

            IAsyncResult ret = d.BeginInvoke(floderName, null, null);  //floderName是CalculateFloderSize的参数

            Console.WriteLine("正在计算中,请耐心等待.....");

            //阻塞,等到调用完成,取出结果

            long size = d.EndInvoke(ret);

            Console.WriteLine("\n计算完成。文件夹{0}的容量为:{1}字节\n", flodername, size);

        }

    程序运行结果如下:

    请注意上述代码加粗语句。

    第04句创建一个引用静态方法CalculateFolderSize()的委托变量d,第08句通过调用委托对象d的BeginInvoke()方法间接地调用静态方法CalculateFolderSize()。这就是异步调用。

    异步调用的关键之处在于静态方法CalculateFolderSize()不是在主线程(即Main()函数所在的线程)中执行的,而在是另一个辅助线程中与主线程代码并执行的。由于存在两个并行执行的线程,所以第08句启动执行静态方法CalculateFolderSize()之后,并不会等待其完成,而是马上执行第09句,输出一个提示信息。

    BeginInvoke()方法是实现异步调用的核心,我们在下面还将对其作详细的介绍,在此处,只需要知道它启动一个异步调用即可。

    方法启动之后,必须有办法取回其计算结果。EndInvoke()方法可以完成这一任务(第11句),但它需要一些额外的信息,这些信息是BeginInvoke()方法启动一个异步调用时提供的。这就是BeginInvoke()方法的返回值ret,一个IAsyncResult类型的对象。

    第11句代码在执行时,如果CalculateFolderSize()方法还未返回,它会停止在这儿等待。当方法CalculateFolderSize()返回时,它取出结果并存放在size变量中,然后马上执行第12句输出结果。

    从这个典型的异步调用程序中,可以知道异步调用的一种通用编程模式是:

    //普通的代码:处于同步执行模式

    IAsyncResult ret=委托变量.BeginInvoke(…);        //启动异步调用

    //可以在这一部分干其它一些事,程序处于异步执行模式

    用于保存方法结果的变量=委托变量.EndInvok(ret);    //结束异步调用

    //普通的代码:处于同步执行模式

 

 

 

8.3 使用轮询等待异步调用完成

    在使用异步调用的上述程序中,用户只是看到一条固定的信息:“正在计算中,请耐心等待…”就没有下文了,显然交互性还是不太好。

    但是做一点小的改进还是可以的。我们可以在程序执行异步调用的过程中,让计算机每隔一段时间向控制台输出一个小点,告诉用户计算正在进行中,从而可以大大改善程序的用户友好性。Main()主函数代码如下:

        static void Main(string[] args)

        {

            //定义一个委托变量并引用静态方法CalculateFolderSize

            CalculateFolderSizeDelegate d = new CalculateFolderSizeDelegate(CalculateFloderSize);

            Console.WriteLine("请输入文件夹名称,如C:\\windows");

            string flodername = Console.ReadLine();

            //通过委托异步调用静态方法CalculateFolderSize

            IAsyncResult ret = d.BeginInvoke(flodername, null, null);

            Console.Write("正在计算中,请耐心等待");

            while (ret.IsCompleted == false)

            {

                Console.Write(".");

                //每隔0.5秒检查一次

                System.Threading.Thread.Sleep(500);

            }

            //阻塞,等到调用完成,取出结果

            long size = d.EndInvoke(ret);

            Console.WriteLine("\n计算完成。文件夹{0}的容量为:{1}字节\n", flodername, size);

            Console.ReadLine();

        }

    程序运行结果如下:

8.4 异步回调

    使用轮询方法不断询问异步调用是否完成,这无疑会浪费不少CPU时间在循环等待上。能不能让异步调用方法在结束时自动调用一个函数,并在这个函数中显示处理结果?

    使用异步回调可以满足这个要求。

    BeginInvoke()方法定义中的最后两个参数是“AsyncCallback callback”和“object asyncState”,这两个参数就是用于异步调用的。

    这个程序现在可以连续输入多个文件夹名称,计算机在后台计算,完成后就在控制台窗口中输出结果。

    (1)定义异步调用委托程序代码.

        public delegate long CalculateFolderSizeDelegate(string folderName);

        private static CalculateFolderSizeDelegate d = new CalculateFolderSizeDelegate(CalculateFloderSize);

    (2)定义一个供异步调用回调的函数。

        //用于回调的函数

        public static void ShowFolderSize(IAsyncResult result)

        {

            long size = d.EndInvoke(result);

            Console.WriteLine("\n文件夹{0}的容量为:{1}字节\n", (string)result.AsyncState, size);

        }

    用于回调的函数返回值类型是void,并且只能有一个IAsyncResult类型的参数result,此参数包含外界传入的信息。

    (3)仔细看一下Main()中的代码。

        static void Main(string[] args)

        {

            string folderName;

            while (true)

            {

                Console.WriteLine("请输入文件夹名称,例如:C:\\windows,输入quit结束程序");

                folderName = Console.ReadLine();

                if (folderName == "quit") break;

                d.BeginInvoke(folderName,ShowFolderSize,folderName);

                   第一个参数:被委托方法中的参数;

                   第二个参数:回调函数;

                   第三人参数:传送到回调函数中的 IAsyncResult result 参数。

            }

        }

    程序运行结果如下:

    请注意调用BeginInvoke()方法的那句代码。BeginInvoke()方法的第2个参数指定当异步调用结束时回调ShowFolderSize()函数,第3个参数AsyncState被填入了要计算的文件名字,此值被BeginInvoke()方法封装到自动创建的一个IAsyncResult类型的对象中,并作为方法实参自动送给回调函数(即本例中回调函数ShowFolderSize()的参数result),回调函数通过这一实参的AsyncState字段获取其值。

    由此可以得出一些结论:

    如果需要将一此额外的信息传送给回函数,就将其放入BeginInvoke()方法的第3个参数asyncState中。注意到这个参数的类型为Object,所以可以放置任意类型的数据。

    如果有多个信息需要传送给回调函数,可以将所要传送的信息封装到一个Struct变量,或者再定义一个类,将信息封装到这个类所创建的对象中,再传递给BeginInvoke()方法。

    经过这样的处理,我们的程序就具备了同时完成多个计算任务的能力,在多CPU的计算机中,这些任务是并行执行的。