C# 程式設計/The .NET Framework/執行緒
執行緒是能夠與其他執行緒併發執行並共享資料的任務。當您的程式啟動時,它會為程式的入口點建立一個執行緒,通常是 Main 函式。因此,您可以將“程式”視為由執行緒組成的。.NET Framework 允許您在程式中使用執行緒以並行執行程式碼。這通常出於以下兩個原因
- 如果執行圖形使用者介面的執行緒執行時間過長的工作,您的程式可能看起來沒有響應。使用執行緒,您可以建立新執行緒來執行任務並將進度報告給 GUI 執行緒。
- 在擁有多個 CPU 或 CPU 擁有多個核心的計算機上,執行緒可以最大限度地利用計算資源,從而加快任務速度。
The System.Threading.Thread 類公開了使用執行緒的基本功能。要建立執行緒,只需使用 ThreadStart 或 ParameterizedThreadStart 委託 建立 Thread 類的例項,該委託指向執行緒應開始執行的程式碼。例如
using System;
using System.Threading;
public static class Program
{
private static void SecondThreadFunction()
{
while (true)
{
Console.WriteLine("Second thread says hello.");
Thread.Sleep(1000); // pause execution of the current thread for 1 second (1000 ms)
}
}
public static void Main()
{
Thread newThread = new Thread(new ThreadStart(SecondThreadFunction));
newThread.Start();
while (true)
{
Console.WriteLine("First thread says hello.");
Thread.Sleep(500); // pause execution of the current thread for half a second (500 ms)
}
}
}
您應該看到以下輸出
Second thread says hello. First thread says hello. First thread says hello. Second thread says hello. First thread says hello. First thread says hello. ...
請注意,需要使用 while 關鍵字,因為一旦函式 返回,執行緒就會退出或終止。
The void ParameterizedThreadStart(object obj) 委託允許您向新執行緒傳遞引數
using System;
using System.Threading;
public static class Program
{
private static void SecondThreadFunction(object param)
{
while (true)
{
Console.WriteLine("Second thread says " + param.ToString() + ".");
Thread.Sleep(500); // pause execution of the current thread for half a second (500 ms)
}
}
public static void Main()
{
Thread newThread = new Thread(new ParameterizedThreadStart(SecondThreadFunction));
newThread.Start(1234); // here you pass a parameter to the new thread
while (true)
{
Console.WriteLine("First thread says hello.");
Thread.Sleep(1000); // pause execution of the current thread for a second (1000 ms)
}
}
}
輸出為
First thread says hello. Second thread says 1234. Second thread says 1234. First thread says hello. ...
雖然我們可以使用 ParameterizedThreadStart 向執行緒傳遞引數,但這不型別安全且使用起來很笨拙。我們可以利用匿名委託線上程之間共享資料,但是
using System;
using System.Threading;
public static class Program
{
public static void Main()
{
int number = 1;
Thread newThread = new Thread(new ThreadStart(delegate
{
while (true)
{
number++;
Console.WriteLine("Second thread says " + number.ToString() + ".");
Thread.Sleep(1000);
}
}));
newThread.Start();
while (true)
{
number++;
Console.WriteLine("First thread says " + number.ToString() + ".");
Thread.Sleep(1000);
}
}
}
請注意匿名委託的正文如何訪問區域性變數 number。
使用匿名委託會導致很多語法、作用域混淆以及缺乏封裝。但是,使用 lambda 表示式,可以緩解其中一些問題。您可以使用非同步委託來傳遞和返回資料,而不是匿名委託,所有這些都是型別安全的。需要注意的是,當您使用非同步委託時,實際上是將新執行緒排隊到執行緒池。此外,使用非同步委託會強迫您使用非同步模型。
using System;
public static class Program
{
delegate int del(int[] data);
public static int SumOfNumbers(int[] data)
{
int sum = 0;
foreach (int number in data) {
sum += number;
}
return sum;
}
public static void Main()
{
int[] numbers = new int[] { 1, 2, 3, 4, 5 };
del func = SumOfNumbers;
IAsyncResult result = func.BeginInvoke(numbers, null, null);
// I can do stuff here while numbers is being added
int sum = func.EndInvoke(result);
sum = 15
}
}
在共享資料示例中,您可能已經注意到,通常,如果不是一直的話,您會得到以下輸出
First thread says 2. Second thread says 3. Second thread says 5. First thread says 4. Second thread says 7. First thread says 7.
人們會期望至少數字是按升序列印的!此問題源於兩段程式碼同時執行的事實。例如,它列印了 3、5,然後列印了 4。讓我們檢查一下可能發生的事情
- 在“第一個執行緒說 2”之後,第一個執行緒對
number進行遞增,使其變為 3,並將其打印出來。 - 然後,第二個執行緒對
number進行遞增,使其變為 4。 - 就在第二個執行緒有機會列印
number之前,第一個執行緒對number進行遞增,使其變為 5,並將其打印出來。 - 然後,第二個執行緒列印了第一個執行緒遞增之前
number的值,即 4。請注意,這可能是由於控制檯輸出緩衝引起的。
此問題的解決方案是同步這兩個執行緒,確保它們的程式碼不會像以前那樣交錯。C# 透過 lock 關鍵字支援這一點。我們可以將程式碼塊放在此關鍵字下
using System;
using System.Threading;
public static class Program
{
public static void Main()
{
int number = 1;
object numberLock = new object();
Thread newThread = new Thread(new ThreadStart(delegate
{
while (true)
{
lock (numberLock)
{
number++;
Console.WriteLine("Second thread says " + number.ToString() + ".");
}
Thread.Sleep(1000);
}
}));
newThread.Start();
while (true)
{
lock (numberLock)
{
number++;
Console.WriteLine("First thread says " + number.ToString() + ".");
}
Thread.Sleep(1000);
}
}
}
需要變數 numberLock,因為 lock 關鍵字只對引用型別起作用,而不是值型別。這一次,您將獲得正確的輸出
First thread says 2. Second thread says 3. Second thread says 4. First thread says 5. Second thread says 6. ...
The lock 關鍵字的工作原理是嘗試對傳遞給它的物件(numberLock)獲得獨佔鎖。它只會在程式碼塊執行完畢後(即在 } 之後)釋放鎖。如果物件已經被鎖定,而另一個執行緒試圖對同一個物件獲得鎖,則該執行緒將阻塞(掛起執行)直到鎖被釋放,然後鎖定該物件。這樣,就可以防止程式碼段交錯。
The Join 方法 of the Thread 類允許執行緒等待另一個執行緒,可以選擇指定超時
using System;
using System.Threading;
public static class Program
{
public static void Main()
{
Thread newThread = new Thread(new ThreadStart(delegate
{
Console.WriteLine("Second thread reporting.");
Thread.Sleep(5000);
Console.WriteLine("Second thread done sleeping.");
}));
newThread.Start();
Console.WriteLine("Just started second thread.");
newThread.Join(1000);
Console.WriteLine("First thread waited for 1 second.");
newThread.Join();
Console.WriteLine("First thread finished waiting for second thread. Press any key.");
Console.ReadKey();
}
}
輸出為
Just started second thread. Second thread reporting. First thread waited for 1 second. Second thread done sleeping. First thread finished waiting for second thread. Press any key.