程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#基礎知識 >> 使用線程之:使用異步編程模型

使用線程之:使用異步編程模型

編輯:C#基礎知識
.NetFramework的異步編程模型從本質上來說是使用線程池來完成異步的任務,異步委托、HttpWebRequest等都使用了異步模型。

這裡我們使用異步委托來說明異步編程模型。

首先,我們來明確一下,對於多線程來說,我們需要關注哪些問題。

“線程是一段執行中的代碼流”,從這句話中,我們可以關注這段代碼流何時開始執行、何時結束、從主線程如何傳遞參數至從子線程、從子線程如何返回結果至主線程?

問題1:在異步編程模型中,子線程何時開始執行?

對於異步編程模型來說,使用BeginXXX來開始執行線程。

我們首先來看一個實例(來自《C#高級編程(第7版)》)。

pulic delegate int TestMethodDelegate(int data,int ms);

static int TestMethod(int data,int ms)

{

Console.WriteLine("TestMethod started.");

Thread.Sleep(ms);

Console.WriteLine("TestMethod completed.");

return data++;

}



static Main()

{

TestMethodDelegate dl=TestMethod;

IAsyncResult ar=dl.BeginInvoke(1,3000,null,null);

//其中,1,3000分別對應委托的兩個參數,後面兩個null位置的參數是兩個固定的參數,是留給異步回調的時候用的(稍後會講到)。

}

問題2:在異步編程模型中,子線程何時結束?

其實,這個問題對於只有一個主線程的單線程程序來說不會成為問題,因為一段代碼執行過程中,執行到這個方法的最後一行,這個方法也就執行結束了。

但在多線程中,由於主線程與子線程有一個從屬關系,加上線程還有前台線程與後台線程之分,使得子線程的結束時機需要加以判斷,判斷規則如下。

(1).如果子線程是後台線程,那麼主線程結束時,不管子線程是否執行完畢,子線程將結束。

(2).如果子線程是前強線程,那麼主線程結束時,子線程將繼續執行,直至結束。

PS:由於線程池裡面的線程均為後台線程,因此,異步編程模型中,只會存在第1種情況。

那麼問題來了:如果子線程為後台線程,且子線程的處理時間非常地長,那主線程如果處理到最後一條代碼時,那子線程就會關閉,那子線程的處理邏輯就沒走完,就會出錯。

我們該如何防止這樣的問題發生呢?

直觀地來說,在主線程中等待子線程完成是一種可行的方案,而要實現這種方案,我們至少需要知道子線程是否已經執行完成?

在異步編程模型中,我們可以使用輪詢(Polling)、等待句柄(WaitHandler)、EndInvoke()方法來達到這樣的效果。

方法1:輪詢(Polling)

我們改寫Main()方法如下:

static Main()

{

TestMethodDelegate dl=TestMethod;

IAsyncResult ar=dl.BeginInvoke(1,3000,null,null);

while(!ar.IsCompleted)

{

//do something

}

//執行到這裡的時候,子線程已經執行完畢了。

}

方法2:等待句柄(WaitHandler)

我們再次改寫Main()方法。

static Main()

{

TestMethodDelegate dl=TestMethod;

IAsyncResult ar=dl.BeginInvoke(1,3000,null,null);

while(true)

{

if(ar.AsyncWaitHandler.WaitOne(50,false))//每等待50毫秒,然後判斷是否子線程是否已完成,如果已完成,則break。

{

//執行到這裡的時候,子線程已經執行完畢了。

break;

}

}

//主線程....

}

方法3:EndInvoke()方法

再次改用Main()方法:

static Main()

{

TestMethodDelegate dl=TestMethod;

IAsyncResult ar=dl.BeginInvoke(1,3000,null,null);

ar.EndInvoke();//會一直等待,直到委托方法執行完成,其實,也可以通過它將子線程的結果返回至主線程中,可以參考後面的問題4.

//主線程....

}

問題3:在異步編程模型中,從主線程如何傳遞參數至從子線程?

這個問題我們其實已經在問題1中有所展示,我們在BeginInvoke的方法中向委托中傳遞了兩個參數。



問題4:在異步編程模型中,從子線程如何返回結果至主線程?

在問題2中,我們使用EndInvoke()等待委托方法執行完成,其實我們也可采用這個方法得到子線程的返回值。

static Main()

{

TestMethodDelegate dl=TestMethod;

IAsyncResult ar=dl.BeginInvoke(1,3000,null,null);

int result=ar.EndInvoke();//得到返回值,返回值類型這裡為int,即委托定義的返回值類型。

//主線程....

}



問題5:異步回調

異步回調很有用,它的出現應該說是極大地擴展了異步模型下的多線程威力。為什麼這麼說?

考慮一種情況,如果主線程需要120秒完成執行,子線程需要80秒,在主線程第90秒的時候開始有啟動子線程,主線程等待子線程結束,最後結束主線程。那麼,整個主線程就至少需要90+80秒的時間來完成執行,中間很明顯有不少主線程等待子線程的時間。有沒有更好的辦法?

如果主線程不需要子線程的返回結果,而子線程結束後還會有一些單獨的處理邏輯(比如清理對象、單獨的處理數據等),我們可以采用異步回調來解決這個問題。

異步回調:簡而言之,就是異步任務完成之後,再回過頭來調用的方法。

先看一個異步回調的例子:

static Main()

{

TestMethodDelegate dl=TestMethod;

IAsyncResult ar=dl.BeginInvoke(1,3000,TestMethodCompleted,dl);

//主線程....

}

static TestMethodCompleted(IAsyncResult ar)

{

//do something

TestMethodDelegate dl=(TestMethodDelegate )ar.AysncState;

dl.EndInvoke();//可以傳遞委托實例進來,在這裡獲得委托線程的結果。

}



在這裡,我們關注BeginInvoke方法的第3個和第4個參數。

第3個參數,TestMethodCompleted即是回調方法,它是一個AsyncCallback類型的委托。

第4個參數是傳遞給回調方法的參數,在回調方法中可以用ar.AsyncState來獲得這個參數。

使用回調方法,必須注意這個方法要從委托線程中調用,而不是從主線程中調用,如果從主線程中調用,那就變成了普通的方法調用。

另外,如果使用Lambda表達式可以實現一些更簡潔優雅的偌。

static Main()

{

TestMethodDelegate dl=TestMethod;

dl.BeginInvoke(1,3000,

ar=>{

int result=dl.EndInvoke(ar);

//do something

},

null) ;

//主線程....

}

//這裡,不需要把一個值賦予BeginInvoke()方法的最後一個參數,因為Lambda表達式可以直接訪問該作用域外部的變量dl。但是,Lambsa表達式的實現代碼仍是從委托線程中調用,只是不是很明顯。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved