深入学习C#委托

一基础学习:

.Net delegate类型:
委托跟回调函数是很有渊源的。回调其实跟通知机制有关,考虑这样一个基本的事件序列:

a对象调用了b对象的某个方法,希望b对象在其方法完成之时调用a对象的某个方法。要实现这样的过程,要求b对象持有a对象的引用(引用一般作为b 对象方法的参数传入),且“知道”a对象的那个特定方法。这个方法就是我们说的那个回调函数。
本质上,.net的委托类型是一种类型安全的指向方法的对象(c++中如函数指针)亦或是指向一系列方法的对象。所谓类型安全指:类型必须匹配,变量或引用不能指向一个不是该类的对象,类型安全由编译器保障,违反类型安全编译时会给出提示。对函数指针而言,就是返回值,入口参数的数目,类型和顺序应一致。

委托一般都是滞后调用,编写委托实现的人一般不知道具体何时该方法被调用。不象传统的C++方法指针,.net的委托是类且具有内建支持,可进行异步和广播式的调用。
委托跟事件event(事件)可自然的契合,一般是事件激发导致委托被调用。

委托对象含有三个重要的信息:1〉被调用方法的地址;2〉此方法的参数(如果有的话);3> 方法的返回值(如果有的话).不同于C++函数指针,.net委托既可指向实例方法也可指向静态方法.
一旦委托被创建且提供了足够的信息,便可在运行时调用它指向的方法.每个委托都可被异步和同步的调用,此点简化了编程工作:使得我们不用手工创建和管理线程对象,就可以在第二个线程中执行我们的方法.(委托可以通过程序来判断进程执行情况,这样可以减少if else switch等判断操作)
   C#中创建委托使用delegate关键字,委托名可以是任何你认为有意义的词.但必须使你的委托匹配你想指向的方法的签名.当C#的编译器处理委托类型时,会自动地生成一个密封(sealed)类,该类继承自System.MulticastDelegate.(这个类的父类是System.Delegate),此类提供了必要的基础结构便于委托持有一个方法的列表,这些方法会在后期的某个时间被调用
通过查看中间语言代码il(用ildasm.exe),可发现编译器会为委托类产生三个公共方法.Invoke();BeginInvoke()和EndIvoke();Invoke()是主要的方法,用于调用委托维护的每个方法.向所有同步调用一样,方法会阻塞调用者直到它执行完毕.Invoke方法也不必显式的调用,它工作于后台.后两个方法需配合使用用于异步执行委托指向的方法. 他们会在独立的线程中执行.
委托的构造方法:

public TypeOfReturn MyDelegate(object target,uint fuctionAddress),

可以看出它需要目标对象,和目标方法的函数地址.这个很易理解,所有方法的调用都遵循object.functionName(),的格式.不过还缺少:参数和返回 值.他们在上面提到的三个方法中给出:

public TypeOfReturnValue Invoke([TypeOfParameter ParaX]*);

//其中TypeOfReturnValue是返回值的类型,中括号里的是可不出现或可出现多个的参数列.

异步方法对:public IAsyncResult BeginInvoke([TypeOfParameter ParaX]*);

public TypeOfReturnValue EndInvoke(IAsyncResult result);

可看出不同处:BeginInvoke先返回一个中间类型,在EndInvoke中才是目标返回值的类型;   

Invoke 方法给人这样的感觉: A--->C ; 而BeginIvoke和EndInvoke: A---->B B---->C 等价于A----C .
引入中间过程的原因是什么呢? 这个 问题以后在讨论吧:它跟异步执行有关,见后面的文章C#委托的异步使用。
下面是委托由编译器产生的伪码表示:
public sealed class DelegateName : System.MulticastDelegate
{
public DelegateName (object target, uint functionAddress);//上面提到的默认构造函数
public delegateReturnValue Invoke(allDelegateInputRefAndOutParams);//Invoke()是主要的方法,用于调用委托维护的每个方法.向所有同步调用一样,方法会阻塞调

                                    //用者直到它执行完毕.Invoke方法也不必显式的调用,它工作于后台.
public IAsyncResult BeginInvoke(allDelegateInputRefAndOutParams,AsyncCallback cb, object state);//后两个方法需配合使用用于异步执行委托指向的方法. 他们会在独立的线程中执行.
public delegateReturnValue EndInvoke(allDelegateRefAndOutParams,IAsyncResult result);//将IAsyncResult最终转换成delegateReturnValue
}
综述:C#委托,编译器会为其对应地产生一个密封(sealed)类,该类有三个核心方法,这些方法的参数和返回值基于委托的定义。

关键字delegate使得你自定义的委托是一个(“is-a”)MulticastDelegate.所有用该关键字修饰的定义类型都是MulticastDelegate的子类,而该类又是Delegate的子类:(public abstract class Delegate : ICloneable, ISerializable)
以下是我的简单学习测试:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace DelegateTest
{
 

    public delegate string MyDelegateType(string firstArg,string secondArg);//创建了一个委托

    public class SomeType {
   
    public static string ClassMethodX(string str1,string str2){
        System.Console.WriteLine("enter the static method <--");
        try
        {
            System.Console.WriteLine("一些对参数的处理工作.......");
            return String.Format("parameter1:{0} and Parameter2{1} come from a static Method", str1, str2);
        }
        finally {
            System.Console.WriteLine("end the static method-->");
        }
    }


    public string InstanceMethodX(string str1,string str2) {

        System.Console.WriteLine("enter the instance method <--");
        try
        {

            System.Console.WriteLine("一些对参数的处理工作.......");
            return String.Format("parameter1:{0} and Parameter2{1} come from a instance Method", str1, str2);
        }
        finally
        {
            System.Console.WriteLine("end the instance method-->");
        }
   
   
   
    }

    }


//程序入口:


    class Program
    {
        static void Main(string[] args)
        {
            MyDelegateType myDelegate = new MyDelegateType(SomeType.ClassMethodX);//创建一个委托,帮定到一个静态方法上(定义的时候不用输入参数)

            System.Console.WriteLine("开始调用委托");
            System.Console.WriteLine(myDelegate("参数一","参数二"));
            System.Console.WriteLine("结束调用委托");

            SomeType aUDTType = new SomeType();//实例化一个自定义的类实例(UDT--表示用户定义类型)
            myDelegate += new MyDelegateType(aUDTType.InstanceMethodX);/*创建新的委托,帮定到一个实例类的方法中(定义到委托中的另一个公有函数中)
             委托的"+="操作等价于其Combine()增加方法,相应的"-="操作等价于Remove()减少方法,
            此二者皆在父类(System.MultcastDelegate/System.Delegate)中定义
            明白+=就是将新的委托成员加入委托列表中,-=为从其中挪去,另有removeALL()清空所有委托成员 */
            System.Console.WriteLine("开始调用委托");
            System.Console.WriteLine(myDelegate("参数一", "参数二"));
            System.Console.WriteLine("结束调用委托");
            System.Console.ReadLine();//完成屏幕停止输入任何键盘操作

        }
    }
}

 

 

深入研究:

C#委托的异步使用

CLR为每个进程维护了一个线程池,初始时它是空的 。但当一个线程被创建且被进程使用之后,并且完成了它的执行时 ,它并不被销毁,而是加入到进程的线程池中。之后,当进程再次需要线程时,它会重新利用池中的线程,这样节省了大量的时间。(那销毁呢?猜测是在一定时间不被调用时会被gc消除)
线程的复杂性:
尽管多线程的概念很简单,但使所有的细节都正确是比较困难的,以下需要被考虑:
1〉线程间的通讯, 线程间的通讯仅有很少的内建机制,所以使用内存是最简单的机制,因为内存对所有同一进程内的所有线程可见并可被访问。
2〉线程的协调,线程的创建很简单,但仍需协调它们的行为。比如一个线程需要等待一个或更多的其他线程完成执行后才可继续自身的执行。
3〉同步资源的使用, 因为同一进程内的所有线程共享进程的内存和资源,必须保证不同的线程不可以同时访问和改变它们,避免资源/内存状态不一致。

当创建复杂的多线程系统时可以使用System.Threading命名空间下的类和类型,包括Thread类本身和其他的如: Mutex(互斥锁),Semaphore(信号量),Monitor(监视器).它们用于资源的同步使用.这些概念都是比较底层且难懂的,最好找些操作系统原理方面的资料参考.
你可往你的程序中加入强大的多线程功能通过两个简单的技术: 异步委托和定时器(asynchronous delegates and timers),对于大部分程序这就是你可能唯一需要的线程技术。

C# 有个非常易用的使方法异步执行的机制:delegates(委托)。委托对象都有一个调用列表,当列表中 仅有一个方法时,它可被异步执行;委托类的BeginInvoke和ndInvoke方法即为此目的。使用步骤如下:
1> 当调用委托对象的BeginInvoke() 方法时,它开始在一个来自线程池的线程中执行委托指向的那个方法,之后立即返回到初始的线程,起初的那个线程将继续执行下面的代码.同时被委托执行的那个线程也在并行执行着.
2> 当你的程序想提取异步执行的方法的结果时,可先检查由BeginInvoke()方法返回的IAsyncResult的IsCompleted属性,或调用EndInvoke()方法等待委托执行完成.
三种标准的使用模式:
1>wait-until-done等待直到完成模式,在产生一个异步方法的执行后(Beginnvoke()),初始线程做一些其他的处理,之后会挂起并等待异步方法的结束,以便继续执行.不同于同步方法的是同步方法一旦被调用便初始线程便不能做任何其他的处理---控制完全转移到被调用的方法那里去了.(这个是两个方法都在执行,初始线程会在执行完别的节点后等待这个执行结果)
2>Polling轮询模式,初始线程周期性的检查新生的线程是否完成了执行,没有的话继续其他的处理.(这个是两个方法都在执行,初始线程会定期检查不会停止)
3>callback回调模式,初始线程不会等待或检查卵生线程是否完成了执行,而是:当被委托引用的方法在卵生(spawned)线程中执行完成时,子线程会调用一个回调方法,该方法在调用EndInvoke()之前处理异步方法的调用结果.(在子线程中调用父线程的方法,父线程是被动的而不是主动的去验证)
注意:原始线程可称为父线程,由其产生(spawn)的线程可称为子线程.
对于BeginInvoke()有一些信息必须知道:
1>当调用BeginInvoke时参数列表含以下的实际参数:
a:被委托引用的函数所需要的参数b: 另外的两个参数: callback和state参数;
2>BeginInvoke()从线程池中取出一个线程并启动引用的方法在新 线程中运行.
3>BeginInvoke()返还给调用线程一个实现了IAsyncResult接口 的 对象 。此接口的引用含有当前执行的异步方法的执行状态信息。

初始线程之后继续执行。
考虑现在很被人鼓吹的Ajax也只不过如这里的思想.
EndInvoke()方法被用于提取由异步执行的方法调用的返回值.且被线程用于释放资源。

EndInvoke 有以下特性:
1>它会用一个 指向IAsyncResult的引用作为参数,IAsyncResult就是BeginInvoke的返回 值,并找到它(IAsyncResult)引用的线程。
2>如果线程池中那个线程已经退出,EndInvoke作如下的事情:
a:清理已退出的线程释放其所占资源。b:找到相应方法的返回值并 将其作为自己的返回值。
如果在EndInvoke调用时,线程池中的那个线程依旧在运行,调用者线程(父线程)会停止并等待它直到完成清理和返回值。因为EndInvoke会对产生的线程做清理工作,所以必须保证针对每一个BeginInvoke()都调用了EndInvoke()(如果你不想资源泄露的话);
3>如果异步方法触发了一个异常,当调用EndInvoke()时异常会被激发.
EndInvoke提供所有源自异步方法的输出,包括ref和out参数.如果委托的相应方法有ref和out参数,它们必须在EndInvoke()方法的参数中出现,且先于IAsyncResult的位置:delegate.EndInvoke(out somePara, iAsyncResult);

@@等待直到完成模式:
namespace WaitUtilDone
{
    public delegate string AsycDelegate(out string outArg, string inArg);//委托定义
    class Program
    {
       
        static void Main(string[] args)
        {
            AsycDelegate myAsycDelegate = (AsycDelegate)AsyncWorker.AsyncMethod;//定义一个委托成员变量,这里用的是简

洁形式
            string fromAsyncMethrod;

           IAsyncResult iAsyncRsl= myAsycDelegate.BeginInvoke(out fromAsyncMethrod, "toAsycMethod", null, null);//委托调用BeginInvoke返回IAsyncResult
            
           
            //主线程中........可在此段时间做其他的事
            Thread.Sleep(2000);//主线程休息2秒钟

            string returnStrFromAsyncMethod = myAsycDelegate.EndInvoke(out fromAsyncMethrod,iAsyncRsl );//调用EndInvoke返回最终结果
            System.Console.WriteLine("the string return from AsyncMethod is {0}",returnStrFromAsyncMethod );
            System.Console.WriteLine("the fromAsyncMethod is :{0}",fromAsyncMethrod);
            System.Console.ReadLine();
        }
    }

    class AsyncWorker {

        public static string AsyncMethod(out string fromThisMethod,string toThisMethod) {
            System.Console.WriteLine("<< enter the AsyncMethod!");
            fromThisMethod = "string form AsyncWork.AsyncMehod as a out parameter";

            System.Console.WriteLine("传入此异步方法的参数:{0}",toThisMethod );
            Thread.Sleep(6000);//该方法会阻塞运行此方法的线程六秒(个人理解是子线程)

            System.Console.WriteLine("end the AsyncMehod >>");

            return "returnValueFromAsyncMethod!";
         }
   
    }
}
上面程序总结:主程序调用委托方法,同时主程序可以休眠也可以继续执行别的程序,子线程完成的是简单的线程等待休眠,完成父线程会调用子线程的BeginInvoke()和EndInvoke()方法最终获取返回值
!关于IAsyncResult:它是BeginInvoke()和EndInvoke()不可或缺的部分.BeginInvoke方法会返回一个AsyncResult类型的对象引用,该类AsyncResult实现了ISyncResult接口,AsyncReslut表示异步方法的状态,对该类需要知道以下重要特征:
1>当调用委托对象的BeginInvoke方法时系统创建一个该类的实例,将此对象的引用返回给ISyncResult 接口.
2> AsyncResult对象 含有一AsyncDelegate的属性,它返回一个指向调用异步方法的委托的引用,该属性是AsyncResult 的属性而不是IAyncResult接口的属性.
3>IsCompleted属性返回一个布尔值,表示异步方法是否被执行完毕.
4>AsyncState属性返回一个作对象的引用,它会作为BeginInvoke方法的调用参数返回的引用是个Object类型.回调模式会用到它.
@@轮询模式:
轮询模式中,原线程初始一个异步方法的调用后,做一些额外的其他工作,然后周期性的检查 IAsyncResult 所指对象的IsCompleted,看异步方法是否完成执行,如果完成,则会调用EndInvoke 并继续执行,否则做一些其他工作,并继续检查.:

using System;
using System.Collections.Generic;

using System.Text;
using System.Threading;

namespace PoolingPattern
{
    public delegate string AsyncDelegate();
    class Program
    {
        static string AsycMethod() {

            System.Console.WriteLine("<< enter the asyncMethod.....");

            Thread.Sleep(3000);

            System.Console.WriteLine("......end the asyncMethod>>");
            return "returnValueFromTheAsyncMethod";
        }
       
        static void Main(string[] args)
        {
            AsyncDelegate myAsyncDele = new AsyncDelegate(AsycMethod );//构造一个委托

            System.Console.WriteLine("main方法中开始 调用 异步方法");
            IAsyncResult iAsyncRslt = myAsyncDele.BeginInvoke(null,null);

            while (!iAsyncRslt.IsCompleted ){
                System.Console.WriteLine("主线程中检查->:..异步调用的方法仍没有 完成...");
                for (long i = 0; i <= 100000000;i++ )
                    ;//做一些 自己的事,这里是 空耗 cpu
             }//轮询 结束 时异步方法应已经完成了

            /*此时可提起异步执行的结果了*/
            string strFromAsyncMethod = myAsyncDele.EndInvoke(iAsyncRslt);
            System.Console.WriteLine("异步方法返回的结果:{0}",strFromAsyncMethod );

            System.Console.WriteLine("main方法结束");
            System.Console.ReadLine();

        }
    }
}
##前两个模式中,原始线程(运行main()方法的线程)仅在卵生线程完成(等待或轮询)时,提取异步方法的结果并继续主线程的运行。

回调模式不同它们:父线程异步调用委托后并不需等待或轮询,当异步方法完成执行时系统会调用一个回调方法去处理异步方法的结果,并调用EndInvoke方法释放相关资源。BeginInvoke方法的最后两个参数就是用与回调目的:
BeginInvoke后两个参数的第一个参数就是回调函数的名称(函数名称可转化为委托!!)。第二个参数state可是null或想传给回调函数的一个对象的引用,可通过方法的IAsyncResult参数(用其AsyncState属性 )访问该对象。参数的类型是object型使用时需显式转型为特定的型别。


@@回调函数的签名必须与AsyncCallback委托的形式一致,此形式要求回调函数需要一个IAsyncResult类型的参数和空的返回值。

void AsyncCallback( IAsyncResult iar );
有多种将回调函数提供给BeginInvoke方法的途径。因为回调参数在BeginInvoke中是类型为AsyncCallback委托类型,你可以直接提供一个函数名,或是先构造一个委托对象。记住特定格式的函数的函数名总是可被编译器隐式转换为一个相应的委托。以下为我写的

小练习:
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Runtime.Remoting.Messaging;
namespace CallBackPattern
{
    class Program
    {
        static void callBackMethod(IAsyncResult asyncRslt) {
            System.Console.WriteLine("<<...Enter the CallbackMethod");
            System.Console.WriteLine("此回调函数在异步方法执行结束后调用的");

            AsyncResult myAsynResult = (AsyncResult)asyncRslt;
            EventHandler asyncDele = (EventHandler)myAsynResult.AsyncDelegate;
            asyncDele.EndInvoke(asyncRslt);//资源回收见下面解释
        
            System.Console.WriteLine("End the CallbackMethod..>>");
        }

        static void myAsyncMethod (object source,System .EventArgs evtArgs){
            System.Console.WriteLine("《《进入异步方法调用区域");
            Thread.Sleep(5000);
           
            System.Console.WriteLine("结束异步方法的调用区域》》");
        }
        static void Main(string[] args)
        {
            System.Console.WriteLine("进入主线程....");

            EventHandler myAsyncDelegate = (EventHandler)myAsyncMethod;/*偷懒了,用的是系统提供的委托,注意方法名可转换为一个委托*/


            IAsyncResult iAsyncRslt= myAsyncDelegate.BeginInvoke(null,null,callBackMethod ,null);
            //使用隐式转换等价下面的方式
            //AsyncCallback myCallback = new AsyncCallback(callBackMethod);
            // IAsyncResult iAsyncRslt= myAsyncDelegate.BeginInvoke(null,null,callBackMethod ,null);
           

            System.Console.WriteLine("主线程结束,等待回调方法被调用.....");
            System.Console.ReadLine();
        }
    }
}

**在回调方法中应该调用EndInvoke方法。且应该处理异步方法的返回值和out参数(如果有的话!)为了调用异步方法的EndInvoke方法你需要有一个指向异步方法的委托引用,它在初始线程中,而不在卵生线程中,如果没有使用BeginInvoke的state参数做任何事,你就可用其传递委托的引用给回调方法:IAsyncResult iar = delegate.BeginInvoke(args,CallWhenDone, delegate);
在回调函数中可通过IAsyncResult类型的参数获取委托的引用。注意:IAsyncResult接口实际指向的是AsyncResult类的实例。尽管

接口不含委托引用但AsyncResult类的对象持有委托的引用。AsyncResult类位于System.Runtime.Remoting.Messaging命名空间。当

通过AsyncResult实例的AsyncDelegate属性获取委托并将之进行合适转型。此后便可调用EndInvoke方法。
using System.Runtime.Remoting.Messaging; // 包含 AsyncResult类
void CallWhenDone( IAsyncResult iar )
{
AsyncResult ar = (AsyncResult) iar; //将接口转型为AsyncResult实例
MyDel del = (MyDel) ar.AsyncDelegate; //获取委托并转型为适当的委托
long Sum = del.EndInvoke( iar ); // 调用EndInvoke
...
}

.........至此委托的异步调用介绍算比较完整了。三个主要模式需仔细掌握,特别是回调模式,很不好理解

 

深入学习C#委托

上一篇:Reusable async validation for WPF with Prism 5


下一篇:C# 截屏