在C#中,從Main()方法開始一個預設的線程,一般稱之為主線程,如果在這個進行一些非常耗CPU的計算,那麼UI介面就會被掛起而處於假死狀態,也就是說無法和使用者進行互動了,特別是要用類似進度條來即時顯示一些提示資訊的時候,這種情況就顯得很糟糕。如果多開一些線程來完成一些耗時的計算,那麼背景工作執行緒也是無法如此更新UI介面中的元素的,比如直接顯示一個提示資訊:label1.Text=outstring,原因很簡單UI屬於預設的主線程,而線程間是不能這樣直接存取彼此的成員的。
如果要解決以上的兩個問題,那麼可以藉助C#中的Delegate和控制項類中的Invoke()方法來搞定。
這裡給出的例子比較簡單,主要思路是:在Main()中啟動其它的線程作為後台進程,其中一個線程是即時顯示當前的時間,一個線程是顯示一些隨機數,這樣一來三個線程同時運行,彼此通過代理來聯絡。
紅色代碼精華
using System;using System.Drawing;using System.Collections;using System.ComponentModel;using System.Windows.Forms;using System.Data;using System.Threading;namespace MutliThreadedWinFormsApp{ public class Form1 : System.Windows.Forms.Form { private Thread currentTimeThread = null; private Thread randomNumbersThread = null; private System.Windows.Forms.Label label1; private System.Windows.Forms.TextBox currentTime; private System.Windows.Forms.Label label2; private System.Windows.Forms.TextBox randomNumbers; private System.Windows.Forms.GroupBox threadManager; private System.Windows.Forms.Button pause; private System.Windows.Forms.Button stop; private System.Windows.Forms.Button start; private System.Windows.Forms.RadioButton manageTime; private System.Windows.Forms.RadioButton manageNumbers; private System.Windows.Forms.RadioButton manageBoth; private System.ComponentModel.Container components = null; public Form1() { InitializeComponent(); //建立新的背景工作執行緒 currentTimeThread = new Thread(new ThreadStart(CountTime)); currentTimeThread.IsBackground = true; randomNumbersThread = new Thread(new ThreadStart(GenerateRandomNumbers)); randomNumbersThread.IsBackground = true; } protected override void Dispose( bool disposing ) { if( disposing ) { if (components != null) { components.Dispose(); } } base.Dispose( disposing ); } UI設計#region UI設計 private void InitializeComponent() { this.label1 = new System.Windows.Forms.Label(); this.currentTime = new System.Windows.Forms.TextBox(); this.label2 = new System.Windows.Forms.Label(); this.randomNumbers = new System.Windows.Forms.TextBox(); this.threadManager = new System.Windows.Forms.GroupBox(); this.manageBoth = new System.Windows.Forms.RadioButton(); this.manageNumbers = new System.Windows.Forms.RadioButton(); this.manageTime = new System.Windows.Forms.RadioButton(); this.pause = new System.Windows.Forms.Button(); this.stop = new System.Windows.Forms.Button(); this.start = new System.Windows.Forms.Button(); this.threadManager.SuspendLayout(); this.SuspendLayout(); // // label1 // this.label1.AutoSize = true; this.label1.Location = new System.Drawing.Point(8, 24); this.label1.Name = "label1"; this.label1.Size = new System.Drawing.Size(79, 13); this.label1.TabIndex = 0; this.label1.Text = "現在的時間:"; // // currentTime // this.currentTime.Location = new System.Drawing.Point(88, 23); this.currentTime.Name = "currentTime"; this.currentTime.ReadOnly = true; this.currentTime.Size = new System.Drawing.Size(157, 20); this.currentTime.TabIndex = 1; // // label2 // this.label2.AutoSize = true; this.label2.Location = new System.Drawing.Point(14, 56); this.label2.Name = "label2"; this.label2.Size = new System.Drawing.Size(43, 13); this.label2.TabIndex = 2; this.label2.Text = "隨機數"; // // randomNumbers // this.randomNumbers.Location = new System.Drawing.Point(16, 72); this.randomNumbers.Name = "randomNumbers"; this.randomNumbers.ReadOnly = true; this.randomNumbers.Size = new System.Drawing.Size(229, 20); this.randomNumbers.TabIndex = 3; // // threadManager // this.threadManager.Controls.Add(this.manageBoth); this.threadManager.Controls.Add(this.manageNumbers); this.threadManager.Controls.Add(this.manageTime); this.threadManager.Controls.Add(this.pause); this.threadManager.Controls.Add(this.stop); this.threadManager.Controls.Add(this.start); this.threadManager.Location = new System.Drawing.Point(16, 104); this.threadManager.Name = "threadManager"; this.threadManager.Size = new System.Drawing.Size(229, 154); this.threadManager.TabIndex = 7; this.threadManager.TabStop = false; this.threadManager.Text = "背景工作執行緒控制"; // // manageBoth // this.manageBoth.Location = new System.Drawing.Point(34, 74); this.manageBoth.Name = "manageBoth"; this.manageBoth.Size = new System.Drawing.Size(104, 16); this.manageBoth.TabIndex = 12; this.manageBoth.Text = "同時運行"; this.manageBoth.CheckedChanged += new System.EventHandler(this.manageBoth_CheckedChanged); // // manageNumbers // this.manageNumbers.Location = new System.Drawing.Point(34, 50); this.manageNumbers.Name = "manageNumbers"; this.manageNumbers.Size = new System.Drawing.Size(104, 16); this.manageNumbers.TabIndex = 11; this.manageNumbers.Text = "更新隨機數線程"; this.manageNumbers.CheckedChanged += new System.EventHandler(this.manageNumbers_CheckedChanged); // // manageTime // this.manageTime.Location = new System.Drawing.Point(34, 26); this.manageTime.Name = "manageTime"; this.manageTime.Size = new System.Drawing.Size(104, 16); this.manageTime.TabIndex = 10; this.manageTime.Text = "更新時間軸程"; this.manageTime.CheckedChanged += new System.EventHandler(this.manageTime_CheckedChanged); // // pause // this.pause.Location = new System.Drawing.Point(84, 115); this.pause.Name = "pause"; this.pause.Size = new System.Drawing.Size(54, 23); this.pause.TabIndex = 9; this.pause.Text = "暫停"; this.pause.Click += new System.EventHandler(this.pause_Click); // // stop // this.stop.Location = new System.Drawing.Point(158, 115); this.stop.Name = "stop"; this.stop.Size = new System.Drawing.Size(51, 23); this.stop.TabIndex = 8; this.stop.Text = "停止"; this.stop.Click += new System.EventHandler(this.stop_Click); // // start // this.start.Location = new System.Drawing.Point(6, 115); this.start.Name = "start"; this.start.Size = new System.Drawing.Size(50, 23); this.start.TabIndex = 7; this.start.Text = "開始"; this.start.Click += new System.EventHandler(this.start_Click); // // Form1 // this.AutoScaleBaseSize = new System.Drawing.Size(5, 13); this.ClientSize = new System.Drawing.Size(253, 271); this.Controls.Add(this.threadManager); this.Controls.Add(this.randomNumbers); this.Controls.Add(this.label2); this.Controls.Add(this.currentTime); this.Controls.Add(this.label1); this.Name = "Form1"; this.Text = "背景工作執行緒和UI的互動"; this.Closing += new System.ComponentModel.CancelEventHandler(this.Form1_Closing); this.threadManager.ResumeLayout(false); this.ResumeLayout(false); this.PerformLayout(); } #endregion [STAThread] static void Main() { Application.Run(new Form1()); } private void start_Click(object sender, System.EventArgs e) { bool bTime = false, bRandomNumbers = false; if( manageTime.Checked || manageBoth.Checked ) bTime = true; if( manageNumbers.Checked || manageBoth.Checked ) bRandomNumbers = true; if( bTime ) currentTimeThread.Start(); if( bRandomNumbers ) randomNumbersThread.Start(); start.Enabled = false; stop.Enabled = true; pause.Enabled = true; } private void stop_Click(object sender, System.EventArgs e) { bool bTime = false, bRandomNumbers = false; if( manageTime.Checked == true || manageBoth.Checked == true ) bTime = true; if( manageNumbers.Checked == true || manageBoth.Checked == true ) bRandomNumbers = true; if( bTime ) { currentTimeThread.Abort(); currentTimeThread.Join(); currentTimeThread = new Thread(new ThreadStart(CountTime)); currentTimeThread.IsBackground = true; } if( bRandomNumbers ) { randomNumbersThread.Abort(); randomNumbersThread.Join(); randomNumbersThread = new Thread(new ThreadStart(GenerateRandomNumbers)); randomNumbersThread.IsBackground = true; } start.Enabled = true; stop.Enabled = false; pause.Enabled = false; } private void pause_Click(object sender, System.EventArgs e) { bool bTime = false, bRandomNumbers = false; if( manageTime.Checked == true || manageBoth.Checked == true ) bTime = true; if( manageNumbers.Checked == true || manageBoth.Checked == true ) bRandomNumbers = true; if( bTime ) { if( 0 != (currentTimeThread.ThreadState & ( ThreadState.Suspended | ThreadState.SuspendRequested ) ) ) currentTimeThread.Resume(); else currentTimeThread.Suspend(); } if( bRandomNumbers ) { if( 0 != (randomNumbersThread.ThreadState & ( ThreadState.Suspended | ThreadState.SuspendRequested ) ) ) randomNumbersThread.Resume(); else randomNumbersThread.Suspend(); } } private void manageTime_CheckedChanged(object sender, System.EventArgs e) { } private void manageNumbers_CheckedChanged(object sender, System.EventArgs e) { } private void manageBoth_CheckedChanged(object sender, System.EventArgs e) { } /**//// <summary> /// 注意其Invoke的使用,其有兩種使用形式 /// public void Invoke(System.Delegate delegate); /// public void Invoke(System.Delegate delegate, object [] args); /// </summary> public void CountTime() { while(true) { Invoke(new UpdateTimeDelegate(updateCurrentTime)); Thread.Sleep(1000); } } public void GenerateRandomNumbers() { int [] randomNumbers = new int[10]; Random r = new Random(); while(true) { for(int i = 0; i < randomNumbers.Length; i++) randomNumbers[i] = r.Next(1, 100); Invoke(new UpdateRandomNumbers(updateRandomNumbers), new object[] { randomNumbers }); Thread.Sleep(500); } } /**//// <summary> /// 負責更新UI介面中時間顯示的函數 /// </summary> private void updateCurrentTime() { currentTime.Text = DateTime.Now.ToLongTimeString(); } /**//// <summary> /// 負責更新UI介面中隨機數顯示的函數 /// </summary> /// <param name="numbers"></param> private void updateRandomNumbers(int [] numbers) { System.Text.StringBuilder sb = new System.Text.StringBuilder(); foreach(int i in numbers) sb.Append(i.ToString()).Append(", "); randomNumbers.Text = sb.ToString(); } private void Form1_Closing(object sender, System.ComponentModel.CancelEventArgs e) { if( (randomNumbersThread.ThreadState & ThreadState.Running) == ThreadState.Running ) randomNumbersThread.Abort(); randomNumbersThread = null; if( (currentTimeThread.ThreadState & ThreadState.Running) == ThreadState.Running ) currentTimeThread.Abort(); currentTimeThread = null; } } public delegate void UpdateTimeDelegate(); public delegate void UpdateRandomNumbers(int [] numbers);}
其實在C# 2.0 中所有的Control類都有Invoke()方法,如果負責更新UI元素的函數不是定義在Main()中,那麼必須首先檢測Control類中的InvokeRequired屬性。舉個例子吧,注意setProgressBarValue()函數中調用自己的方式.
//在背景工作執行緒中更新主視窗進度條 public void setProgressBarValue(ProgressBar progressBar1,int value) { if (progressBar1.InvokeRequired) { object[] parameters = new object[] { value }; progressBar1.Invoke(new setProgressBarValueDelegate(setProgressBarValue), parameters); } else progressBar1.Value = value; } 這裡的一些代碼參考了http://www.codeproject.com 的例子.