【.NET線程--進階(二)】--小問題?不簡單……


       上篇博客對線程的基本方法進行了解析,並使用了兩個實例來演示了線程方法的使用,在前幾篇博客中有很多小問題沒有詳細說明,比如進程的同步異步問題,線程執行時參數的傳遞和獲取參數等等,該篇博客將會針對這些小問題來展開討論。

1、進程的同步和異步

      

  1.1 進程同步


       就是在發出一個功能調用時,在沒有得到結果之前,該調用就不返回。也就是必須一件一件事做,等前一件做完了才能做下一件事。就像早上起床后,先洗涮,然后才能吃飯,不能在洗涮沒有完成時,就開始吃飯。按照這個定義,其實絕大多數函數都是同步調用。但是一般而言,我們在說同步、異步的時候,特指那些需要其他部件協作或者需要一定時間完成的任務。

       最常見的例子就是MessageBox。該函數發送一個消息給某個窗口,在對方處理完消息之前,這個函數不返回。當對方處理完畢以后,該函數才把消息處理函數所返回的result值返回給調用者。

  1.2 異步


       異步的概念和同步相對。當一個異步過程調用發出后,調用者不能立刻得到結果。實際處理這個調用的部件在完成后,通過狀態、通知和回調來通知調用者。
       以多線程為例,在運行一個程序時會把CPU分成多個線程來運行程序,這些互不影響的線程在同一時間運行實現了異步執行。
      

2、線程執行方法


  2.1 調用線程時傳遞參數

        細心的童鞋應該能夠發現前篇博客在創建線程時傳遞給線程的參數使用的是ThreadStart委托,而且給線程執行的方法並沒有參數,這樣在調用時就不用關心傳遞參數的問題。其實.NET為我們提供了兩種線程委托,一個是ThreadStart委托,在執行時不需要使用參數,另外一個是ParameterizedThreadStart委托,從它的字面意思上可以讀出它是參數化的委托,也就是說使用該種委托將會能夠為線程函數傳遞參數。也就是說 ParameterizedThreadStart 委托提供了一種簡便方法,可以在調用 Thread.Start 方法重載時將包含數據的對象傳遞給線程。代碼如下:
class Program
{
    static void Main(string[] args)
    {
        
            //使用ParameterizedThreadStart委托調用有參數的方法
            Thread th = new Thread(new ParameterizedThreadStart(PrintToScreen.PrintString));
            th.Start("This is parameterized thread.");  //在Start方法中為方法執行參數
    }
}

/// <summary>
/// 需要執行的打印文字的類
/// </summary>
class PrintToScreen {
    /// <summary>
    /// 執行打印字符串的線程方法
    /// </summary>
    /// <param name="str">需要打印的字符串</param>
    public static void PrintString(object str) {
        Console.Write(str); //向控制台打印字符串
        Console.Read(); 
    }
}

運行結果:


        上面使用 ParameterizedThreadStart 委托傳遞數據,但該方法並不安全,因為 Thread.Start 方法重載接受任何對象。一種替代方法是將線程過程和數據封裝在幫助器類中,並使用 ThreadStart 委托執行線程過程,如下的代碼示例:

class Program
{
    static void Main(string[] args)
    {
        PrintToScreen pts = new PrintToScreen("This is parameterized thread.",4);
        //使用ThreadStart委托調用有參數的方法
        Thread th = new Thread(pts.PrintString);
        th.Start();  //在Start方法中為方法執行參數

    }
}

/// <summary>
/// 需要執行的打印文字的類
/// </summary>
class PrintToScreen {
    private string str; //需要打印的字符串
    private int j;  //需要打印字符串的個數

    /// <summary>
    /// 構造函數制定類參數
    /// </summary>
    /// <param name="str">需要打印的字符串</param>
    /// <param name="j">打印次數</param>
    public PrintToScreen(string str, int j) {
        this.str = str;
        this.j = j;
    }
    /// <summary>
    /// 執行打印字符串的線程方法
    /// </summary>
    public void PrintString() {

        for (int i = 0; i < j; ++i)
        {
            Console.WriteLine(i.ToString()+" "+str); //向控制台打印字符串
        }
        Console.Read();
    }
}

運行結果:


 2.2 執行線程后獲取返回值


  2.2.1 IAsyncResult+EndInvoke

         在.NET中使用線程的方法很多,使用委托的BeginInvoke和EndInvoke方法就是其中之一。BeginInvoke方法可以使用線程異步地執行委托所指向的方法。然后通過EndInvoke方法獲得方法的返回值(EndInvoke方法的返回值就是被調用方法的返回值),或是確定方法已經被成功調用,下面我們介紹兩種方法從EndInvoke中獲取返回值。

using System;  
using System.Collections.Generic;  
using System.Linq;  
using System.Text;  
using System.Threading;  
     
namespace MyThread  
{  
    class Program
    {
	    static void Main(string[] args)
	{
	
	    NewDelegate nd = NewTask;   //聲明委托,並為委托指定方法
	    IAsyncResult asyncresult = nd.BeginInvoke(2000, null, null);  //執行委托,並獲取異步操作的狀態
	        
	    //while循環,判斷異步操作是否完成,沒有完成的話將會打印*表示提醒用戶等待
	    while (!asyncresult.IsCompleted) {
	        Console.Write("*");
	        Thread.Sleep(100);
	    }
	
	    //調用EndInvoke獲取返回值
	    int result = nd.EndInvoke(asyncresult);
	    Console.Write(result);
	    Console.Read();
	
	}
	
	    /// <summary>
	    /// 獲取隨機數
	    /// </summary>
	    /// <param name="intTime">方法的參數,執行的時間值</param>
	    /// <returns>int 返回不大於1000的隨機整數</returns>
	    public static int NewTask(int intTime) {
	        Console.WriteLine("Starting the task!");
	        Thread.Sleep(intTime);//線程停頓1000秒
	        Random rand = new Random(); //創建一個隨機數生成器
	        int n = rand.Next(1000);    //獲取一個隨機數
	        Console.WriteLine("The task is completed!");
	        return n;   //返回隨機數
	    }
	
	    /// <summary>
	    /// 委托,指定執行的方法
	    /// </summary>
	    /// <param name="intTime">方法的參數,執行的時間值</param>
	    /// <returns>int類型</returns>
	    private delegate int NewDelegate(int intTime);
    }    
} 

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

   2.2.2 使用回調方式返回結果

       上面介紹的方法雖然可以成功返回結果,也可以給用戶一些提示,但在這個過程中,整個程序就象死了一樣,要想在調用的過程中,程序仍然可以正常做其它的工作,就必須使用異步調用的方式。下面我們使用GUI程序來編寫一個例子。
       下面的代碼通過異步的方式訪問的form上的一個textbox,因此,需要按ctrl+f5運行程序。並在form上放一些其他的可視控件,然在點擊button1后,其它的控件仍然可以使用,就象什么事都沒有發生過一樣,在10秒后,在textbox1中將輸出100。
       Note:不能直接按F5運行程序,否則無法在其他線程中訪問這個textbox,關於如果在其他線程中訪問GUI組件,前篇博客中已經提到。

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }

    private delegate int MyMethod();   //聲明委托執行的方法類型

    /// <summary>
    /// 獲取返回值
    /// </summary>
    /// <returns>int 獲取小於1000的隨機整數</returns>
    private int method()
    {
        Thread.Sleep(10000);    //線程停頓10秒后向返回值
        Random rand = new Random(); //創建隨機數生成器
        int j = rand.Next(1000);    //獲取小於1000的隨機數
        return j;   //返回該隨機數
    }

    /// <summary>
    /// 異步執行的回調函數
    /// </summary>
    /// <param name="asyncResult">異步操作的狀態</param>
    private void MethodCompleted(IAsyncResult asyncResult)
    {
        if (asyncResult == null)
        {
            return;
        }

        //執行函數,獲取返回值
        textBox1.Text = (asyncResult.AsyncState as MyMethod).EndInvoke(asyncResult).ToString();
    }

    private void button1_Click(object sender, EventArgs e)
    {
        MyMethod my = method;   //聲明委托並為委托指定執行方法
        IAsyncResult asyncResult = my.BeginInvoke(MethodCompleted, my); //異步執行方法
    }

}

         對於BeginInvoke方法,它有多種重載方式,這里用的是它的兩個參數重載。第一個參數是回調方法委托類型,這個委托只有一個參數,就是IAsyncResult,如MethodCompleted方法所示。當method方法執行完后,系統會自動調用MethodCompleted方法。BeginInvoke的第二個參數需要向MethodCompleted方法中傳遞一些值,一般可以傳遞被調用方法的委托,如上面代碼中的my。這個值可以使用IAsyncResult.AsyncState屬性獲得。

結語


       上面提到執行部件和調用者通過三種途徑返回結果:狀態、通知和回調。可以使用哪一種依賴於執行部件的實現,除非執行部件提供多種選擇,否則不受調用者控制。如果執行部件用狀態來通知,那么調用者就需要每隔一定時間檢查一次,效率就很低(有些初學多線程編程的人,總喜歡用一個循環去檢查某個變量的值,這其實是一種很嚴重的錯誤)。如果是使用通知的方式,效率則很高,因為執行部件幾乎不需要做額外的操作。至於回調函數,其實和通知沒太多區別。

        看似簡單的小問題實則有很多注意項,為了能達到優化和安全的目的,小問題也不簡單。


注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2021 ITdaan.com