一、使用多線程的幾種方式 不需要傳遞參數,也不需要返回參數 ThreadStart是一個委托,這個委托的定義為void ThreadStart(),沒有參數與返回值。 class Program { static void Main(string[] args) { for (int i = 0; ...
一、使用多線程的幾種方式
-
不需要傳遞參數,也不需要返回參數
ThreadStart是一個委托,這個委托的定義為void ThreadStart(),沒有參數與返回值。
class Program
{
static void Main(string[] args)
{
for (int i = 0; i < 30; i++)
{
ThreadStart threadStart = new ThreadStart(Calculate);
Thread thread = new Thread(threadStart);
thread.Start();
}
Thread.Sleep(2000);
Console.Read();
}
public static void Calculate()
{
DateTime time = DateTime.Now;//得到當前時間
Random ra = new Random();//隨機數對象
Thread.Sleep(ra.Next(10,100));//隨機休眠一段時間
Console.WriteLine(time.Minute + ":" + time.Millisecond);
}
}
-
需要傳遞單個參數
ParameterThreadStart委托定義為void ParameterizedThreadStart(object state),有一個參數但是沒有返回值。
class Program
{
static void Main(string[] args)
{
for (int i = 0; i < 30; i++)
{
ParameterizedThreadStart tStart = new ParameterizedThreadStart(Calculate);
Thread thread = new Thread(tStart);
thread.Start(i*10+10);//傳遞參數
}
Thread.Sleep(2000);
Console.Read();
}
public static void Calculate(object arg)
{
Random ra = new Random();//隨機數對象
Thread.Sleep(ra.Next(10, 100));//隨機休眠一段時間
Console.WriteLine(arg);
}
}
-
使用專門的線程類(常用)
使用線程類可以有多個參數與多個返回值,十分靈活!
class Program
{
static void Main(string[] args)
{
MyThread mt = new MyThread(100);
ThreadStart threadStart = new ThreadStart(mt.Calculate);
Thread thread = new Thread(threadStart);
thread.Start();
//等待線程結束
while (thread.ThreadState != ThreadState.Stopped)
{
Thread.Sleep(10);
}
Console.WriteLine(mt.Result);//列印返回值
Console.Read();
}
}
public class MyThread//線程類
{
public int Parame { set; get; }//參數
public int Result { set; get; }//返回值
//構造函數
public MyThread(int parame)
{
this.Parame = parame;
}
//線程執行方法
public void Calculate()
{
Random ra = new Random();//隨機數對象
Thread.Sleep(ra.Next(10, 100));//隨機休眠一段時間
Console.WriteLine(this.Parame);
this.Result = this.Parame * ra.Next(10, 100);
}
}
-
使用匿名方法(常用)
使用匿名方法啟動線程可以有多個參數和返回值,而且使用非常方便!
class Program
{
static void Main(string[] args)
{
int Parame = 100;//當做參數
int Result = 0;//當做返回值
//匿名方法
ThreadStart threadStart = new ThreadStart(delegate()
{
Random ra = new Random();//隨機數對象
Thread.Sleep(ra.Next(10, 100));//隨機休眠一段時間
Console.WriteLine(Parame);//輸出參數
Result = Parame * ra.Next(10, 100);//計算返回值
});
Thread thread = new Thread(threadStart);
thread.Start();//多線程啟動匿名方法
//等待線程結束
while (thread.ThreadState != ThreadState.Stopped)
{
Thread.Sleep(10);
}
Console.WriteLine(Result);//列印返回值
Console.Read();
}
}
- 使用委托開啟多線程(多線程深入)
-
用委托(Delegate)的BeginInvoke和EndInvoke方法操作線程
BeginInvoke方法可以使用線程非同步地執行委托所指向的方法。然後通過EndInvoke方法獲得方法的返回值(EndInvoke方法的返回值就是被調用方法的返回值),或是確定方法已經被成功調用。我們可以通過四種方法從EndInvoke方法來獲得返回值。
class Program
{
private delegate int NewTaskDelegate(int ms);
private static int newTask(int ms)
{
Console.WriteLine("任務開始");
Thread.Sleep(ms);
Random random = new Random();
int n = random.Next(10000);
Console.WriteLine("任務完成");
return n;
}
static void Main(string[] args)
{
NewTaskDelegate task = newTask;
IAsyncResult asyncResult = task.BeginInvoke(2000, null, null);
//EndInvoke方法將被阻塞2秒
int result = task.EndInvoke(asyncResult);
Console.WriteLine(result);
Console.Read();
}
}
-
使用IAsyncResult.IsCompleted屬性來判斷非同步調用是否完成
class Program
{
private delegate int NewTaskDelegate(int ms);
private static int newTask(int ms)
{
Console.WriteLine("任務開始");
Thread.Sleep(ms);
Random random = new Random();
int n = random.Next(10000);
Console.WriteLine("任務完成");
return n;
}
static void Main(string[] args)
{
NewTaskDelegate task = newTask;
IAsyncResult asyncResult = task.BeginInvoke(2000, null, null);
//等待非同步執行完成
while (!asyncResult.IsCompleted)
{
Console.Write("*");
Thread.Sleep(100);
}
// 由於非同步調用已經完成,因此, EndInvoke會立刻返回結果
int result = task.EndInvoke(asyncResult);
Console.WriteLine(result);
Console.Read();
}
}
-
使用WaitOne方法等待非同步方法執行完成
WaitOne的第一個參數表示要等待的毫秒數,在指定時間之內,WaitOne方法將一直等待,直到非同步調用完成,併發出通知,WaitOne方法才返回true。當等待指定時間之後,非同步調用仍未完成,WaitOne方法返回false,如果指定時間為0,表示不等待,如果為-1,表示永遠等待,直到非同步調用完成。
class Program
{
private delegate int NewTaskDelegate(int ms);
private static int newTask(int ms)
{
Console.WriteLine("任務開始");
Thread.Sleep(ms);
Random random = new Random();
int n = random.Next(10000);
Console.WriteLine("任務完成");
return n;
}
static void Main(string[] args)
{
NewTaskDelegate task = newTask;
IAsyncResult asyncResult = task.BeginInvoke(2000, null, null);
//等待非同步執行完成
while (!asyncResult.AsyncWaitHandle.WaitOne(100, false))
{
Console.Write("*");
}
int result = task.EndInvoke(asyncResult);
Console.WriteLine(result);
Console.Read();
}
}
-
使用回調方式返回結果
要註意的是"my.BeginInvoke(3,300, MethodCompleted, my)",BeginInvoke方法的參數傳遞方式:
前面一部分(3,300)是其委托本身的參數。
倒數第二個參數(MethodCompleted)是回調方法委托類型,他是回調方法的委托,此委托沒有返回值,有一個IAsyncResult類型的參數,當method方法執行完後,系統會自動調用MethodCompleted方法。
最後一個參數(my)需要向MethodCompleted方法中傳遞一些值,一般可以傳遞被調用方法的委托,這個值可以使用IAsyncResult.AsyncState屬性獲得。
class Program
{
private delegate int MyMethod(int second, int millisecond);
//線程執行方法
private static int method(int second, int millisecond)
{
Console.WriteLine("線程休眠" + (second * 1000 + millisecond) + "毫秒");
Thread.Sleep(second * 1000 + millisecond);
Random random = new Random();
return random.Next(10000);
}
//回調方法
private static void MethodCompleted(IAsyncResult asyncResult)
{
if (asyncResult == null || asyncResult.AsyncState == null)
{
Console.WriteLine("回調失敗!!!");
return;
}
int result = (asyncResult.AsyncState as MyMethod).EndInvoke(asyncResult);
Console.WriteLine("任務完成,結果:" + result);
}
static void Main(string[] args)
{
MyMethod my = method;
IAsyncResult asyncResult = my.BeginInvoke(3,300, MethodCompleted, my);
Console.WriteLine("任務開始");
Console.Read();
}
}
其他組件的BeginXXX和EndXXX方法
在其他的.net組件中也有類似BeginInvoke和EndInvoke的方法,如System.Net.HttpWebRequest類的BeginGetResponse和EndGetResponse方法。其使用方法類似於委托類型的BeginInvoke和EndInvoke方法,例如:
class Program
{
//回調函數
private static void requestCompleted(IAsyncResult asyncResult)
{
if (asyncResult == null || asyncResult.AsyncState==null)
{
Console.WriteLine("回調失敗");
return;
}
HttpWebRequest hwr = asyncResult.AsyncState as HttpWebRequest;
HttpWebResponse response = (HttpWebResponse)hwr.EndGetResponse(asyncResult);
StreamReader sr = new StreamReader(response.GetResponseStream());
string str = sr.ReadToEnd();
Console.WriteLine("返迴流長度:"+str.Length);
}
static void Main(string[] args)
{
HttpWebRequest request =
(HttpWebRequest)WebRequest.Create("http://www.baidu.com");
//非同步請求
IAsyncResult asyncResult = request.BeginGetResponse(requestCompleted, request);
Console.WriteLine("任務開始");
Console.Read();
}
}
二、線程的狀態控制
-
前臺線程與後臺線程
Thread.IsBackground屬性為true則是後臺線程,為false則是前臺線程。後臺線程與前臺線程區別如下:
a.當在主線程中創建了一個線程,那麼該線程的IsBackground預設是設置為false的。
b.當主線程退出的時候,IsBackground=false的線程還會繼續執行下去,直到線程執行結束。只有IsBackground=true的線程才會隨著主線程的退出而退出。
c.當初始化一個線程,把Thread.IsBackground=true的時候,指示該線程為後臺線程。後臺線程將會隨著主線程的退出而退出。
d.原理:只要所有前臺線程都終止後,CLR就會對每一個活在的後臺線程調用Abort()來徹底終止應用程式
-
由線程類(Thread)啟動動的線程狀態控制
使用System.Threading.ThreadState與System.Diagnostics.ThreadState枚舉判斷線程狀態,與Thread.ThreadState 屬性配合使用。
System.Threading.ThreadState枚舉狀態:
System.Diagnostics.ThreadState枚舉狀態:
註意:您的代碼在任何情況下都不應使用線程狀態來同步線程的活動。
由委托啟動的線程的狀態控制
委托的EndInvoke方法阻止當前線程運行,直到委托非同步執行完成。
IAsyncResult.IsCompleted屬性表示委托非同步執行是否完成。
委托的WaitOne方法等待非同步方法執行完成。
三、多線程訪問GUI界面的處理
-
多線程在GUI編程時出現的問題
在GUI編程時,如果你從非創建這個控制項的線程中訪問這個控制項或者操作這個控制項的話就會拋出這個異常。這是微軟為了保證線程安全以及提高代碼的效率所做的改進,但是也給大家帶來很多不便。
-
通過設置處理
設置System.Windows.Forms.Control.CheckForIllegalCrossThreadCalls = false;在你的程式初始化的時候設置了這個屬性,而且在你的控制項中使用的都是微軟Framework類庫中的控制項的話,系統就不會再拋出你上面所說的這個錯誤了。
-
通過委托處理(建議使用)
//按鈕事件
private void button1_Click(object sender, EventArgs e)
{
Thread thread = new Thread(Flush);
thread.IsBackground = true;//設置成後臺線程
thread.Start();
}
//線程執行的方法
private void Flush()
{
//定義委托
Action action = delegate()
{
this.textBox1.AppendText(DateTime.Now.ToString() + "\r\n");
};
while (true)
{
//判斷能否到當前線程操作該組件
if (this.textBox1.InvokeRequired)
{
//不在當前線程上操作
this.textBox1.Invoke(action);//調用委托
}
else
{
//在當前線程上操作
this.textBox1.AppendText(DateTime.Now.ToString() + "\r\n");
}
Thread.Sleep(1000);
}
註意:使用該方式不會有無響應的情況發生,強烈建議使用該方式。此方式不會發生界面無響應的關鍵點:調用this.textBox1.Invoke(action)就是在擁有this.textBox1對象的線程(不一定是當前線程,多數是主線程)上調用action委托,若action委托指向的方法執行時間過長就會使得界面無響應!而該方式中action委托指向的方法執行時間極為短。
-
調用控制項的Invoke和BeginInvoke方法的區別
在多線程編程中,我們經常要在工作線程中去更新界面顯示,而在多線程中直接調用界面控制項的方法是錯誤的做法,正確的做法是將工作線程中涉及更新界面的代碼封裝為一個方法,通過Invoke或者BeginInvoke去調用,兩者的區別就是Invoke導致工作線程等待,而BeginInvoke則不會。
而所謂的"一面響應操作,一面添加節點"永遠只能是相對的,使UI線程的負擔不至於太大而以而已,因為界面的正確更新始終要通過UI線程去做,我們要做的事情是在工作線程中包攬大部分的運算,而將對純粹的界面更新放到UI線程中去做,這樣也就達到了減輕UI線程負擔的目的了。
-
Application.DoEvents()調用消息處理程式
在耗時的迴圈的UI更新的方法中,插入Application.DoEvents(),會使界面獲得響應,Application.DoEvents()會調用消息處理程式。
使用BackgroundWorker組件
主要的事件及參數:
1.DoWork—當執行BackgroundWorker.RunWorkerAsync方法時會觸發該事件,並且傳遞DoWorkEventArgs參數;
2.ProgressChanged—操作處理中獲得的處理狀態變化,通過BackgroundWorker.ReportProgress方法觸發該事件,並且傳遞ProgressChangedEventArgs,其中包含了處理的百分比,這個參數在UI界面上設置progressbar控制項。
3.RunWorkerCompleted—非同步操作完成或中途終止會觸發該事件。如果需要提前終止執行後臺操作,可以調用BackgroundWorker.CancelAsync方法。在處理DoWork事件的函數中檢測BackgroundWorker.CancellationPending屬性是否為true,如果是true,則表示用戶已經取消了非同步調用,同時將DoWorkEventArgs.Cancel屬性設為true(傳遞給處理DoWork事件的函數的第二個參數),這樣當退出非同步調用的時候,可以讓處理RunWorkerCompleted事件的函數知道是正常退出還是中途退出。
主要的方法:
1. BackgroundWorker.RunWorkerAsync—"起動"非同步調用的方法有兩次重載RunWorkerAsync(),RunWorkerAsync(object argument),第二個重載提供了一個參數,可以供非同步調用使用。(如果有多個參數要傳遞怎麼辦,使用一個類來傳遞他們吧)。調用該方法後會觸發DoWork事件,並且為處理DoWork事件的函數傳遞DoWorkEventArg參數,其中包含了RunWorkerAsync傳遞的參數。在相應DoWork的處理函數中就可以做具體的複雜操作。
2. BackgroundWorker.ReportProgress—需要在一個冗長的操作中向用戶不斷反饋進度,這樣的話就可以調用的