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
...
}

.........至此委托的異步調用介紹算比較完整了。三個主要模式需仔細掌握,特別是回調模式,很不好理解

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM