線程與線程池以及任務和協商式取消
一、線程
使用System.Threading命名空間下的Thread類即可創建專有線程
var t = new Thread(() => Console.WriteLine("new thread"));
構造函數有如下四個版本
Thread(ThreadStart start);
public Thread(ThreadStart start, int maxStackSize);
public Thread(ParameterizedThreadStart start);
public Thread(ParameterizedThreadStart start, int maxStackSize)
其中ThreadStart、ParameterizedThreadStart 是兩個委托類型,表示將要執行的函數,一個能接受參數,一個不接受參數。原型如下:
public delegate void ParameterizedThreadStart(object obj);
public delegate void ThreadStart();
使用有參版本如下:
var t = new Thread(state => Console.WriteLine($"My name is {state}"));
t.Start("HK");調用Start如下的重載方法即可,內部會使用這個參數并調用相應的委托。
public void Start(object parameter)
一般來說,你將要執行的函數一般會將這個參數進行強制轉換,以便你使用。如下所示
var t = new Thread(state =>
{
Point p = (state as Point) ?? throw new ArgumentException("無效的參數類型");//強制轉型
Console.WriteLine($"The sum is {p.X + p.Y}");
});t.Start(new Point(1,2));
用到的類如下:
public class Point
{
public int X { get; private set; }
public int Y { get; private set; }
public Point(int x, int y)
{
this.X = x;
this.Y = y;
}
}函數體執行完之后,該線程就會被釋放,但是該創建該線程的實例對象的內存不會釋放,直到GC回收該內存。
使用該類創建線程有明顯的一個缺點,不能使用有返回值的委托。并且線程不能重用,一經創建,執行完函數體之后,便被銷毀了,由于創建線程是有資源消耗的,對于客戶端程序可能不明顯,但對于高性能的服務端程序,頻繁的創建線程會浪費許多系統資源。所以要用下面要介紹的線程池。
二、線程池
顧名思義,就是存儲線程的一個池子,當有任務進來的時候,線程池分配一個線程去執行該任務,當該任務執行完之后,線程不會不銷毀,而是由線程池回收,待有新任務到來時,再去分配線程執行新任務。線程池中的線程只有第一次創建時才會損耗性能,創建完成之后,便不會了。
同樣是在System.Threading命名空間下,使用ThreadPool靜態類如下的靜態方法即可
public static bool QueueUserWorkItem(WaitCallback callBack, object state);
public static bool QueueUserWorkItem(WaitCallback callBack);
其中WaitCallback 也是一個委托類型,與ParameterizedThreadStart委托類型是一樣的
public delegate void WaitCallback(object state);
public delegate void ParameterizedThreadStart(object obj);
注意與Thread類不同,使用QueueUserWorkItem方法之后,該任務會自動加入待執行列表,而不用像Thread類一樣顯示調用Start方法。注意不管是QueueUserWorkItem還是Start,執行之后函數體(任務)并不一定會馬上執行,而是等待調度,得到執行權之后才會執行。在系統資源不緊張的情況下,通常會立即執行。注意使用線程池創建的線程默認是后臺線程,使用Thread類創建的實例,默認是前臺線程,在Thread類的實例對象下,可以設置IsBackground屬性為false從而把線程設置為后臺線程。還可以通過Thread類的靜態對象Thread.CurrentThread來獲得當前線程實例對象,從而設置線程的前后臺屬性。前臺線程和后臺線程的區別見第三點。
使用如下:
ThreadPool.QueueUserWorkItem((state) =>
{
Console.WriteLine("new Task");
});這會造成工作項進入一個待執行隊列,然后線程池會分配線程去執行這個工作項,線程池剛開始是空的,一但有工作項進來,就會創建線程去執行這個工作項,工作項完成之后,線程會由線程池回收復用,繼續去執行隊列中的工作項。當線程池中空閑的線程為0時,且待執行隊列還有工作項的時候,線程池會創建新的線程(如果系統資源足夠的話,否則只能等待有空閑的線程去執行隊列中的工作項)去執行這個工作項。
然而線程返回值還是不能直接得到,要想直接得到返回值,可以通過System.Threading.Tasks命名空間下的Task<T>類。
三、前臺線程和后臺線程以及協商式取消
在CLR中,線程要么是前臺線程,要么是后臺線程,每個應用程序至少有一個前臺線程,所有前臺線程停止運行后,CLR將強制終止仍在運行的后臺線程。
如下所示:
static void Main(string[] args)
{
var t = new Thread(() =>
{
Thread.Sleep(1000);
Console.WriteLine("Background Thread");
});
t.IsBackground = true;//設置為后臺線程
t.Start();
Console.WriteLine("Main thread over");
}設置t.IsBackground = false
有時我們想讓執行的工作取消,可以使用協商式取消的方式,為什么叫協商式,是因為要雙方都要遵守一個約定。我們使用System.Threading命名空間下的CancellationTokenSource類。然后我們可以通過該類實例下的Token屬性獲得一個值類型實例,這個值類型是CancellationToken,同樣是在System.Threading命名空間下。雙方使用這個實例便可實現協作式取消操作。如下所示:
private static void OP(int n,CancellationToken token)
{
Thread.CurrentThread.IsBackground = false;
int sum = 0;
for (int i = 0; i < n; i++)
{
if(token.IsCancellationRequested)//如果是通過Task執行的,這里應該拋出一個異常(token.ThrowIfCancellationRequested),因為任務可以有返回值,如果像這里一樣處理,則無法知道該任務是正常結束了還是取消了。
{ //另外注意,通過Thread類的實例或者是使用ThreadPool創建的線程是無法在調用線程直接捕獲異常然后進行處理的,所以這里不可以選擇拋出異常,一旦拋出異常則應用程序就會終止。
Console.WriteLine("work is not over");
Console.WriteLine(sum.ToString());
return;
}
sum += i;
Thread.Sleep(100);
}
Console.WriteLine("work is over");
Console.WriteLine(sum.ToString());
}
static void Main(string[] args)
{
var cts = new CancellationTokenSource();
ThreadPool.QueueUserWorkItem((statue) => OP(10,cts.Token));
Console.ReadLine();
cts.Cancel();
}
提前按下回車鍵
正常完成





浙公網安備 33010602011771號