c# 多線程實例學習

實例程序說明
  本文的實例程序包括一個列表框、三個按鈕。程序使用一個新的線程來運行一個後臺處理,結果在列表框中顯示。按鈕button1啓動一個計算平方的線程。按鈕button2停止後臺處理線程。按鈕button3退出程序。程序運行情況如圖1所示。
  
  使用線程
  首先創建運行在新線程上的後臺任務。表1所示的代碼執行一個相當長的運行處理----一個無限循環。
  
  表1、後臺處理程序
  private void BackgroundProcess()
  {
  int i= 1;
  while(true)
  {
  // 向列表框增加一個項目
  listBox1.Items.Add("Iterations: " + i.ToString ());
  i ++;
  Thread.Sleep(2000); // 指定線程休眠的時間
  }
  }
  
  這段代碼無限循環,每次執行時在列表框中加入一個項目。
  
  在規定好一個工作的處理代碼以後,就需要將這段代碼分配給一個線程,並且啓動它。爲此需要使用線程對象(Thread object),它是.NET架構類中System.Threading命名空間的一部分。在實例化一個新的線程類時,需要把在線程類構造器中執行的代碼塊的一個引用傳送給該實例。表2所示的代碼創建一個新的線程對象,並且將BackgroundProcess的一個引用傳送給該對象。
  
  表2、線程的使用
  Thread t1,t2; // 說明爲窗體類成員
  t1 = new Thread(new ThreadStart(BackgroundProcess));
  t1.Start(); // 以上2行放置在窗體的load事件中
  
  ThreadStart表示在線程上執行的方法,這裏是一個到BackgroundProcess方法的委派對象。在C#中,一個委派是一個類型安全、面向對象的函數指針。在實例化該線程後,可以通過調用線程的Start()方法來開始執行代碼。
  
  控制線程
  在線程啓動以後,可以通過調用線程對象的方法來控制線程的狀態。可以通過調用Thread.Sleep方法來暫停一個線程的執行,這個方法可以接收一個整型值,用來決定線程休眠的時間。對於本文的實例程序,爲了讓列表項目增加的速度變慢,在其中放入了一個Sleep方法的調用。
  
  可以通過調用Thread.Sleep(System.Threading.Timeout.Infinite)來讓線程進入休眠狀態,但是,這個調用的休眠時間是不確定的。要中斷這個休眠,可以調用Thread.Interrupt方法。
  
  通過調用Thread.Suspend方法可以掛起線程。掛起可以暫停一個線程,直到另一個線程調用Thread.Resume爲止。休眠和掛起的區別是,掛起並不立刻讓線程進入一個等待的狀態,線程並不會掛起,直到.NET runtime認爲現在已經是一個安全的地方來掛起它了,而休眠則會立刻讓線程進入一個等待的狀態。
  
  表3、停止線程的執行
  private void button2_Click
  (object sender, System.EventArgs e)
  {   t1.Abort();   }
  
  Thread.Abort方法可以停止一個線程的執行。本文的實例程序通過加入一個按鈕button2來停止後臺處理,在事件處理程序中調用了Thread.Abort方法,如表3所示。
  
  這就是多線程的強大之處。用戶界面的響應很快,因爲用戶界面運行在一個單獨的線程中,而後臺的處理運行在另外一個線程中。在用戶按下按鈕button2時,就會馬上得到響應,並且停止後臺處理。
  
  通過多線程程序傳送數據
  在實際工作中,還需要使用到多線程的許多複雜特性。其中一個問題就是如何將程序的數據由線程類的構造器傳入或者傳出。對於放到另外一個線程中的過程,既不能傳參數給它,也不能由它返回值,因爲傳入到線程構造器的過程是不能擁有任何參數或者返回值的。爲了解決這個問題,可以將過程封裝到一個類中,這樣,方法的參數就可使用類中的字段。
  
  本文給出了一個簡單的例子,計算一個數的平方。爲了在一個新的線程中使用這個過程,將它封裝到一個類中,如表4所示。
  
  使用表5所示的代碼在一個新的線程上啓動CalcSquare過程。
  
  表4、計算一個數的平方  表5、在一個新的線程上啓動CalcSquare過程
  public class SquareClass
  {
  public double Value;
  public double Square;
  public void CalcSquare()
  {
  Square = Value * Value;
  }
  }  private void button1_Click(object sender, System.EventArgs e)
  {
  SquareClass oSquare =new SquareClass();
  t2 = new Thread(new ThreadStart(oSquare.CalcSquare));
  oSquare.Value = 30;
  t2.Start();
  }
  
  在上述例子中,線程啓動後,並沒有檢查類中的square值,因爲即使調用了線程的start方法,也不能確保其中的方法馬上執行完。要從另一個線程中得到需要的值,有幾種方法,其中一種方法就是在線程完成的時候觸發一個事件。表6所示的代碼爲SquareClass加入了事件聲明。
  
  表6、爲SquareClass加入事件聲明
  public delegate void EventHandler(double sq); // 說明委派類型
  public class SquareClass
  {
  public double Value;
  public double Square;
  public event EventHandler ThreadComplete; // 說明事件對象
  public void CalcSquare()
  {
  Square = Value * Value;
  // 指定事件處理程序
  ThreadComplete+=new EventHandler(SquareEventHandler);
  if( ThreadComplete!=null)ThreadComplete(Square); // 觸發事件
  }
  public static void SquareEventHandler(double Square ) // 定義事件處理程序
  {   MessageBox.Show(Square.ToString ());   }
  }
  
  對於這種方法,要注意的是事件處理程序SquareEventHandler運行在產生該事件的線程t2中,而不是運行在窗體執行的線程中。
  
  同步線程
  在線程的同步方面,C#提供了幾種方法。在上述計算平方的例子中,需要與執行計算的線程同步,以便等待它執行完並且得到結果。另一個例子是,如果在其它線程中排序一個數組,那麼在使用該數組前,必須等待該處理完成。爲了實現同步,C#提供了lock聲明和Thread.Join方法。
  
  lock聲明
  
  表7、使用lock聲明
  public void CalcSquare1()
  {
  lock( typeof(SquareClass))
  {
  Square = Value * Value;
  }
  }
  
  lock可以得到一個對象引用的唯一鎖,使用時只要將該對象傳送給lock就行了。通過這個唯一鎖,可以確保多個線程不會訪問共享的數據或者在多個線程上執行的代碼。要得到一個鎖,可以使用與每個類關聯的System.Type對象。System.Type對象可以通過使用typeof運算得到,如表7所示。
  
  Thread.Join方法
  
  表8、使用Thread.Join方法
  private void button1_Click(object sender, System.EventArgs e)
  {
  SquareClass oSquare =new SquareClass();
  t2 = new Thread(new ThreadStart(oSquare.CalcSquare));
  oSquare.Value = 30;
  t2.Start();
  if( t2.Join (500) )
  {
  MessageBox.Show(oSquare.Square.ToString ());
  }
  }
  
  Thread.Join方法可以等待一個特定的時間,直到一個線程完成。如果該線程在指定的時間內完成了,Thread.Join將返回True,否則它返回False。在上述平方的例子中,如果不想使用觸發事件的方法,可以調用Thread.Join的方法來確定計算是否完成了。代碼如表8所示。
相關錯誤處理技巧:
新建一個c# window應用程序
窗體上面放一個listBox1和一個button1
代碼這樣寫的
private void button1_Click(object sender, EventArgs e)
        {
            Thread mT = new Thread(new ThreadStart(myThread));          
            mT.Start();
        }
        private void myThread()
        {
            while (true )
            {              
                listBox1.Items.Add(DateTime.Now.ToString());
            }
編譯沒有問題
當點擊button的時候出現錯誤提示:
未處理 System.InvalidOperationException
Message=”線程間操作無效: 從不是創建控件“listBox1”的線程訪問它。”
是怎麼回事兒呢??

--------------------------------------------------------------------------------

VS2005中調用不是本線程創建的windows控件會報錯。
應該使用控件的InvokeRequired屬性來判斷是否需要Invoke,如果是則使用控件Invoke的方法來執行一個委託.
如:
if(this.txtBox.invokerequired)
{
Invoke(new EventHandler(ChangeText),new object[]{sender,EventArgs.Empty});
}
void ChangeText(object sender,EventArgs e){
this.txtBox.Text = “2″;
}

--------------------------------------------------------------------------------

類似如下:
    public partial class Form1 : Form
    {
        private delegate void myDel();
        public Form1()
        {
            InitializeComponent();
        }
        private void button1_Click(object sender, EventArgs e)
        {
            Thread mT = new Thread(new ThreadStart(myThread));          
            mT.Start();
        }
        private void myThread()
        {
            int i=0;
            while (i<5)
            {
                if (this.listBox1.InvokeRequired)
                {
                    Invoke(new myDel(MyMethod));
                }
                i++;
              
            }
        }
        private void MyMethod()
        {
            listBox1.Items.Add(DateTime.Now.ToString());
        }
    }
  
  結論
  本文通過一個實例程序說明了C#中線程的使用和控制方法,探討了如何通過多線程程序傳送數據和線程的同步問題。根據本文的分析可知,在C#中,使用線程是很簡單的。C#支持建立自由線程的應用,提高了資源的利用率,程序的響應速度也得到了改善。當然也帶來了數據傳送和線程同步等問題。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章