其實,恐怕大家在看完(一)之後便已經在心中產生了一個呼之欲出的想法——委托就是函數指針!最初,我也曾有過這樣的想法,只是並未在相應的文獻資料中找到支撐。然而這是後話,是當我自己對於委托的理解到達可以寫得出第一篇隨筆之後才可能有的見解。因此,先前的幾篇雖名為《我眼中委托的真正面貌》,實則是我自己對於委托的一個探索過程,因此,我以委托常見且較有代表性的幾個用法來組織文章的脈絡。個人感覺,對於一個未知的對象,這樣一個由淺入深的過程其實是很有必要的。至少,我希望可以幫那些和我一樣對於委托並不是太了解的朋友們少走一些彎路。
不過,想法歸想法,我說過暫時沒有找到相應的理論支撐。因此,我仍然以探索的態度來闡述自己的一些觀點,歡迎大家批評指正,同時希望已經完全摸透委托機制的高人們不吝賜教。
前不久一個偶然的機會搜到了這樣一篇文章:
從函數指針到代理(C#代理入門)
作者是木頭象朋友。他在文中為我們闡述了這樣的觀點:委托是函數指針功能的擴展。
其實,個人感覺這句話闡明的觀點還是有一定的道理的,感興趣的朋友們可以支持一下木頭象朋友的帖子。這個觀點究竟正確與否,我們姑且不論。然而,我們卻可以以此為論題,來探討一下委托比之函數指針究竟有哪些功能上的擴展。
C#中要了解一個對象自身的一些特質其實很簡單,我們甚至無需依賴於MSDN,你只需聲明相應的對象,在此之後點一個“.”,C#編譯器的智能感知功能便會自動列出這個對象的相關屬性及其方法的一些簡單描述。
我們用一個實例化的委托對象進行如上操作,便可大致了解委托對象的一些特有的方法和屬性了。這裡我們討論幾個關鍵方法的使用方法:
9.異步委托的實現
using System.Threading;
namespace AsyncDelegate
{
class Program
{
//定義委托
delegate void MyDelegate();
static void Main(string[] args)
{
//聲明委托對象
MyDelegate ObjDelegate = new MyDelegate(ObjDelegateFun);
//使用異步方式
IAsyncResult ObjIr = ObjDelegate.BeginInvoke(null,null);
Console.WriteLine("委托方法執行中.");
Thread.Sleep(5000);
ObjDelegate.EndInvoke(ObjIr);
Console.ReadLine();
}
static private void ObjDelegateFun()
{
Thread.Sleep(5000);
Console.WriteLine("委托方法調用成功!");
}
}
}
所謂異步委托,主要用到了委托對象的BeginInvoke()方法以及EndInvoke()方法。和同步執行的Invoke()方法相比,這兩個方法具有如下的特性:
BeginInvoke()方法以異步方式開啟目標方法,也就是說,其在調用之後會立即返回,即使是執行時間相當長的目標方法對於BeginInvoke()而言也不會帶來任何的阻塞。如果是依靠委托來執行耗時相當長的方法,並且我們不急於得到方法的返回值(亦或是操控其他對象的效果)或者我們根本就不用得到方法的返回值,則以這種異步方式來執行無疑是最合適的。
EndInvoke()方法是與BeginInvoke()方法配對執行的,用於檢索調用結果。相對前者而言,如果異步調用的方法尚未執行完畢,則EndInvoke()將一直遭到阻塞,直至異步方法執行完畢。其傳入參數為調用BeginInvoke()時返回的IAsyncResult型對象。
有關BeginInvoke()、EndInvoke()方法,以及IAsyncResult接口對象的詳細用法everx朋友在他的隨筆中進行了較為全面的闡述,在這裡為大家推薦一下:
異步委托的用法
我們再來看上述代碼。Main()方法的第二句以異步方式執行委托方法,方法則在剛開始執行時被迫暫停5秒,然而,執行時我們會發現"委托方法執行中...."這句話幾乎是在程序開始的瞬間就被顯示出來了。這說明,BeginInvoke()在執行後是馬上返回的,並未遭到任何的阻塞。在這之後,我們將Main()方法同樣暫停5秒,而後以EndInvoke()獲得委托方法的返回效果:"委托方法調用成功!"。這樣,我們實現了委托的異步調用。
大家發現了嗎?這種異步委托機制和C#中的多線程機制有異曲同工之妙!而事實上,在相應的目標方法中看線程號和主線程確實是不一樣的。不過,在查閱了相關的一些資料之後,我還是得知了其中的一些細微的差別。這裡推薦給大家一篇來自Kuffy Wang朋友的文章:
異步委托與多線程的區別
下面我們使用回調方式來執行異步委托:
9(2)回調式異步委托
using System.Threading;
namespace AsyncDelegate
{
class Program
{
//定義委托
delegate void MyDelegate();
static void Main(string[] args)
{
//聲明委托對象
MyDelegate ObjDelegate = new MyDelegate(ObjDelegateFun);
//使用異步方式
IAsyncResult ObjIr = ObjDelegate.BeginInvoke(new AsyncCallback(CallbackFun), ObjDelegate);
Console.WriteLine("委托方法執行中.");
Console.ReadLine();
}
static private void ObjDelegateFun()
{
Thread.Sleep(5000);
Console.WriteLine("委托方法調用成功!");
}
static private void CallbackFun(IAsyncResult ar)
{
MyDelegate ObjDelegate = (MyDelegate)ar.AsyncState;
ObjDelegate.EndInvoke(ar);
Console.WriteLine("回調方法執行完畢!");
Console.ReadLine();
}
}
}
對比以上的兩段代碼,我們不難發現其中的差別,與上一段代碼相比,我們在這裡使用了BeginInvoke()的另一個重載方法。其中的一個參數即為BeginInvoke()方法指定的回調方法,在相應的目標方法執行完畢之後將調用該回調方法,並且可以在相應的回調用法中調用結束動作。
這裡值得順帶一提的是,有關我寫上一篇隨筆時留下的關於Control.Invoke()方法的疑問。
大家可能會發現,本節中提到的[delegate].Invoke()方法與上節中提到的Control.Invoke()方法在名稱書寫上是一樣的,那麼他們在功能上是否存在著些許的相似之處呢?以下是我做出的總結:
Return Work Thread
Control.Invoke 完成工作 強制於 UI Thread
Control.BeginInvoke 立即 強制於 UI Thread
[delegate].Invoke 完成工作 Call Invoke 的 Thread
[delegate].BeginInvoke 立即 新的背景 Thread
從中大家可以發現一個關鍵的問題,不論委托對象也好,Control對象也好,他們的Invoke ()方法都是以絕對同步的方式執行,而BeginInvoke()方法則是以絕對異步的方式執行。
在這裡,我之所以強調“絕對” 兩個字,是為了提醒讀者雖然“異步”與“多線程”間存在著密切的聯系,但異步並不等於在原程序的基礎上開設子線程,同步亦並非一定要針對單線程的程序而言。
我們不妨再以上一篇隨筆中“跨線程操控控件”這段代碼來說明問題,當然為說明問題方便,我做了部分調整:
using System.Threading;
namespace MulTrdDelegate
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
//定義委托類型
public delegate void TreadDelgate();
//初始化子線程對象
private Thread demoThread = null;
private void button1_Click(object sender, EventArgs e)
{
demoThread = new Thread(new ThreadStart(ThreadProcUnsafe));
demoThread.Start();
}
public void ThreadProcUnsafe()
{
if (this.textBox1.InvokeRequired)
{
TreadDelgate Objdelegate = new TreadDelgate(SetText);
this.Invoke(Objdelegate, new object[] {});
MessageBox.show("委托方法已返回!");
}
else
{
SetText();
}
}
private void SetText()
{
Thread.sleep(5000);//人為延長方法的執行時間
string text = "這個控件的內容是由子線程實現的";
this.textBox1.Text = text;
}
}
}
大家留心我在代碼中用紅筆勾注的這句代碼,如果說委托方法在調用時立即返回,那麼這句代碼會馬上執行。不過,當大家運行時就會發現,這句代碼是在SetText()方法返回之後才執行的——這便是Control.Invoke()方法所起到的作用了。大家注意,本身這是一個多線程程序,按理說this.Invoke(Objdelegate, new object[] {});將相應的委托方法強制到主線程去執行,子線程是不受影響的,不過這裡卻是完全按照單線程的方式在執行的。這就是所謂的“絕對同步”,也就是說即使是多線程也同樣會強制按照單線程的方式來執行。這裡和本節提到的[delegate].Invoke ()方法執行方式完全一致。
感興趣的讀者也可以嘗試將this.Invoke()改為this.BeginInvoke(),查看一下運行效果。沒錯,和本節提到的[delegate].BeginInvoke ()方法執行方式完全一致。
也就是說,不管當前運行的環境是多線程還是單線程。同步方法會強制程序按照單線程的方式執行,異步方法則強制程序按照多線程方式執行。
其實,從寫第一篇關於委托的隨筆開始,就不斷有園友問我:什麼時候采用委托?在這裡,我並不打算對這個問題做出正面回答。原因很簡單——程序效果是單一的,實現方式則是多種多樣的。這裡提供本人在學習委托過程中的幾個相當有代表性的實例,僅僅是為了幫助讀者們以自己的方式來理解委托的用途。每個人有每個人看待事物的獨特方式,別人的思想到你身上不一定就行得通。
真心希望以上的幾篇隨筆可以幫助讀者對委托有一個更深入的認識,屆時大家自然可以真真正正的總結出適合於自己思路的委托使用環境,以及,你自己眼中所看到的委托的真正面貌。