1. 程式人生 > >BeginInvoke和EndInvoke方法

BeginInvoke和EndInvoke方法

開發語言:C#3.0

IDEVisual Studio 2008

本系列教程主要包括如下內容:

1.  BeginInvokeEndInvoke方法

2.  Thread

3. 執行緒池

4. 執行緒同步基礎

5. 死鎖

6. 執行緒同步的7種方法

7. 如何線上程中訪問GUI元件

一、執行緒概述

在作業系統中一個程序至少要包含一個執行緒,然後,在某些時候需要在同一個程序中同時執行多項任務,或是為了提供程式的效能,將要執行的任務分解成多個子任務執行。這就需要在同一個程序中開啟多個執行緒。我們使用C#編寫一個應用程式(控制檯或桌面程式都可以),然後執行這個程式,並開啟windows
工作管理員,這時我們就會看到這個應用程式中所含有的執行緒數,如下圖所示。



如果工作管理員沒有“執行緒數”列,可以【檢視】>【選擇列】來顯示“執行緒計數”列。從上圖可以看出,幾乎所有的程序都擁有兩個以上的執行緒。從而可以看出,執行緒是提供應用程式效能的重要手段之一,尤其在多核CPU的機器上尤為明顯。

二、用委託(Delegate)BeginInvokeEndInvoke方法操作執行緒

C#中使用執行緒的方法很多,使用委託的BeginInvokeEndInvoke方法就是其中之一。BeginInvoke方法可以使用執行緒非同步地執行委託所指向的方法。然後通過EndInvoke方法獲得方法的返回值(

EndInvoke方法的返回值就是被呼叫方法的返回值),或是確定方法已經被成功呼叫。我們可以通過四種方法從EndInvoke方法來獲得返回值。

三、直接使用EndInvoke方法來獲得返回值

當使用BeginInvoke非同步呼叫方法時,如果方法未執行完,EndInvoke方法就會一直阻塞,直到被呼叫的方法執行完畢。如下面的程式碼所示: 複製程式碼 using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace MyThread
{
    
class Program
    {
        
privatestaticint newTask(int ms)
        {
            Console.WriteLine(
"任務開始");
            Thread.Sleep(ms);
            Random random 
=new Random();
            
int n = random.Next(10000);
            Console.WriteLine(
"任務完成");
            
return n;
        }

        
privatedelegateint NewTaskDelegate(int ms);
             
        
        
staticvoid Main(string[] args)
        {
            NewTaskDelegate task 
= newTask;
            IAsyncResult asyncResult 
= task.BeginInvoke(2000nullnull);

            // EndInvoke方法將被阻塞2秒int result = task.EndInvoke(asyncResult);           
            Console.WriteLine(result);
        }
    }
}
複製程式碼

    在執行上面的程式後,由於newTask方法通過Sleep延遲了2秒,因此,程式直到2秒後才輸出最終結果(一個隨機整數)。如果不呼叫EndInvoke方法,程式會立即退出,這是由於使用BeginInvoke建立的執行緒都是後臺執行緒,這種執行緒一但所有的前臺執行緒都退出後(其中主執行緒就是一個前臺執行緒),不管後臺執行緒是否執行完畢,都會結束執行緒,並退出程式。關於前臺和後臺執行緒的詳細內容,將在後面的部分講解。

    讀者可以使用上面的程式做以下實驗。首先在Main方法的開始部分加入如下程式碼:

Thread.Sleep(10000);

    以使Main方法延遲10秒鐘再執行下面的程式碼,然後按Ctrl+F5執行程式,並開啟企業管理器,觀察當前程式的執行緒數,假設執行緒數是4,在10秒後,執行緒數會增至5,這是因為呼叫BeginInvoke方法時會建立一個執行緒來非同步執行newTask方法,因此,執行緒會增加一個。

四、使用IAsyncResult asyncResult屬性來判斷非同步呼叫是否完成雖然上面的方法可以很好地實現非同步呼叫,但是當呼叫EndInvoke方法獲得呼叫結果時,整個程式就象死了一樣,這樣做使用者的感覺並不會太好,因此,我們可以使用asyncResult來判斷非同步呼叫是否完成,並顯示一些提示資訊。這樣做可以增加使用者體驗。程式碼如下: 複製程式碼 staticvoid Main(string[] args)
{
    NewTaskDelegate task 
= newTask;
    IAsyncResult asyncResult 
= task.BeginInvoke(2000nullnull);
 
    
while (!asyncResult.IsCompleted)
    {
        Console.Write(
"*");
        Thread.Sleep(
100);
    }
    
// 由於非同步呼叫已經完成,因此, EndInvoke會立刻返回結果    int result = task.EndInvoke(asyncResult);           
    Console.WriteLine(result);
}
複製程式碼
上面程式碼的執行結果如下圖所示。



    由於是非同步,所以“*”可能會在“任務開始”前輸出,如上圖所示。

五、使用WaitOne方法等待非同步方法執行完成使用WaitOne方法是另外一種判斷非同步呼叫是否完成的方法。程式碼如下: 複製程式碼 staticvoid Main(string[] args)
{
    NewTaskDelegate task 
= newTask;
    IAsyncResult asyncResult 
= task.BeginInvoke(2000nullnull);

    
while (!asyncResult.AsyncWaitHandle.WaitOne(100false))
    {
         Console.Write(
"*");              
    }

    
int result = task.EndInvoke(asyncResult);
    Console.WriteLine(result);
}
複製程式碼

    WaitOne的第一個引數表示要等待的毫秒數,在指定時間之內,WaitOne方法將一直等待,直到非同步呼叫完成,併發出通知,WaitOne方法才返回true。當等待指定時間之後,非同步呼叫仍未完成,WaitOne方法返回false,如果指定時間為0,表示不等待,如果為-1,表示永遠等待,直到非同步呼叫完成。

六、使用回撥方式返回結果上面介紹的幾種方法實際上只相當於一種方法。這些方法雖然可以成功返回結果,也可以給使用者一些提示,但在這個過程中,整個程式就象死了一樣(如果讀者在GUI程式中使用這些方法就會非常明顯),要想在呼叫的過程中,程式仍然可以正常做其它的工作,就必須使用非同步呼叫的方式。下面我們使用GUI程式來編寫一個例子,程式碼如下: 複製程式碼 privatedelegateint MyMethod();
privateint method()
{
    Thread.Sleep(
10000);
    
return100;
}
privatevoid MethodCompleted(IAsyncResult asyncResult)
{
    
if (asyncResult ==nullreturn;
    textBox1.Text 
= (asyncResult.AsyncState as 
    MyMethod).EndInvoke(asyncResult).ToString();
}

privatevoid button1_Click(object sender, EventArgs e)
{

    MyMethod my 
= method;
    IAsyncResult asyncResult 
= my.BeginInvoke(MethodCompleted, my);
}
複製程式碼

    要注意的是,這裡使用了BeginInvoke方法的最後兩個引數(如果被呼叫的方法含有引數的話,這些引數將作為BeginInvoke的前面一部分引數,如果沒有引數,BeginInvoke就只有兩個引數了)。第一個引數是回撥方法委託型別,這個委託只有一個引數,就是IAsyncResult,MethodCompleted方法所示。當method方法執行完後,系統會自動呼叫MethodCompleted方法。BeginInvoke的第二個引數需要向MethodCompleted方法中傳遞一些值,一般可以傳遞被呼叫方法的委託,如上面程式碼中的my。這個值可以使用IAsyncResult.AsyncState屬性獲得。

    由於上面的程式碼通過非同步的方式訪問的form上的一個textbox,因此,需要按ctrl+f5執行程式(不能直接按F5執行程式,否則無法在其他執行緒中訪問這個textbox,關於如果在其他執行緒中訪問GUI元件,並在後面的部分詳細介紹)。並在form上放一些其他的可視控制元件,然在點選button1後,其它的控制元件仍然可以使用,就象什麼事都沒有發生過一樣,在10秒後,在textbox1中將輸出100

七、其他元件的BeginXXXEndXXX方法

    在其他的.net元件中也有類似BeginInvokeEndInvoke的方法,如System.Net.HttpWebRequest類的BeginGetResponseEndGetResponse方法,下面是使用這兩個方法的一個例子:


複製程式碼 privatevoid requestCompleted(IAsyncResult asyncResult)
{
    
if (asyncResult ==nullreturn;
    System.Net.HttpWebRequest hwr 
= asyncResult.AsyncState as System.Net.HttpWebRequest;
    System.Net.HttpWebResponse response 
= 
(System.Net.HttpWebResponse)hwr.EndGetResponse(asyncResult);
    System.IO.StreamReader sr 
=new 
System.IO.StreamReader(response.GetResponseStream());
    textBox1.Text 
= sr.ReadToEnd();
}
privatedelegate System.Net.HttpWebResponse RequestDelegate(System.Net.HttpWebRequest request);

privatevoid button1_Click(object sender, EventArgs e)
{
    System.Net.HttpWebRequest request 
= 
    (System.Net.HttpWebRequest)System.Net.WebRequest.Create(
"http://www.cnblogs.com");
    IAsyncResult asyncResult 
=request.BeginGetResponse(requestCompleted, request);     
}
複製程式碼