環境:
- window 10
- .NetFramework 4.7
- vs2019 16.4.5
一、線程的狀態
線程的狀態可以從枚舉ThreadState
中查看到:
public enum ThreadState
{
Running = 0x0,
StopRequested = 0x1,
SuspendRequested = 0x2,
Background = 0x4,
Unstarted = 0x8,
Stopped = 0x10,
WaitSleepJoin = 0x20,
Suspended = 0x40,
AbortRequested = 0x80,
Aborted = 0x100
}
我們先拋開Background
不談,說一下其他的九種狀態:
- Unstarted :
當我們var thread = new Thread(()=>{})執行後,這個線程就被創建了,此時它並不會被cpu執行,它的狀態爲:Unstarted - Running :
當我們調用thread.Start()方法後,這個線程就安排給cpu調用了,此時它的狀態爲Running - Stopped、StopRequested :
當我們創建的線程正常執行後,它沒代碼執行了,也就是死掉了,此時它的狀態爲Stopped ,注意:這裏說的是正常執行完畢,並不是被強制終止(thead.Abort())。至於StopRequested,從名字可以看出它是處於Stopped狀態之前的,可能是速度太快,我調試的時候並沒有捕獲到。最後,線程結束後不能重新發起 - WaitSleepJoin :
這種狀態也稱之爲休眠狀態,由thread.Join()或Thread.Sleep()引發,前者是主線程調用子線程的Join方法導致主線程阻塞(直到子線程執行完畢才能解除阻塞)並切換到WaitSleepJoin狀態,後者是子線程內部調用Sleep方法導致子線程休眠並切換到WaitSleepJoin狀態。從這個方法的名字中也可以看到它主要是由Join和Sleep方法引起的線程休眠(程序鎖也會導致休眠),而解除這種狀態的方法是thread.Interrupt()。注意:thread.Interrupt()會導致線程在阻塞處拋出異常,注意捕捉。 - Suspended、SuspendRequested:(掛起操作不被建議使用,標記爲廢棄的api,.netcore中不支持Suspend/Resume方法的調用)
線程被掛起時的狀態,由thread.Suspend()引發。注意:進程的掛起和休眠不是一個意思,線程的休眠必須指定休眠的時間,時間一到就自動解除休眠,而掛起則不然,它必須被其他線程解除掛起纔行,也可以這樣說:線程的休眠是線程本身根據需要引發的,而掛起則是由管理者決定的。當一個線程處於WaitSleepJoin狀態時管理者仍然可以將這個線程標記爲掛起,只不過這個由於線程已經自己休眠了,所以就暫時安排個SuspendRequested狀態,等它的休眠期已過就立刻打上Suspend狀態。還有一個重要的地方,線程被掛起時會拋出異常,如果沒有處理線程就會死掉,如果處理了,代碼的執行位置就會改變,這一點和休眠是很不同的。最後,處於Suspend狀態的線程只能由管理者調用Resume()方法解除掛起 - Aborted、AbortRequested(結束線程最好的方法是通過信號燈讓線程自己中斷,而不是強制Abort:C# Thread.Abort方法真的讓線程停止了嗎?,.netcore中不支持Abort方法的調用)
線程被終止狀態,這個和Stopped不同,Stopped是正常執行完畢,而Aborted是被強制終止並拋出異常。
注意:
不要使用Suspend和Resume方法來同步線程的活動。當你Suspend線程時,您無法知道線程正在執行什麼代碼。如果在安全權限評估期間線程持有鎖時掛起線程,則AppDomain中的其他線程可能會被阻塞。如果線程在執行類構造函數時Suspend,則試圖使用該類的AppDomain中的其他線程將被阻塞。死鎖很容易發生。
二、測試代碼
2.1 測試Join()方法阻塞主線程
using System;
using System.Collections;
using System.Data;
using System.IO;
using System.Threading;
namespace TestDI
{
class Program
{
public static void Main(string[] args)
{
Test6();
Console.WriteLine("ok");
Console.ReadLine();
}
private static void Test6()
{
var threadMain = Thread.CurrentThread;
var thread = new Thread(() =>
{
Thread.Sleep(2000);
for (int i = 0; i < 10000; i++)
{
File.AppendAllText("d:\\temp.txt", i + "\r\n");
}
//由於這個線程被主線程執行thread.Join(),從而導致主線程被阻塞,所以主線程狀態爲:WaitSleepJoin
Console.WriteLine("主線程狀態:" + threadMain.ThreadState);
});
//線程thread剛被創建,還未啓動,狀態爲:Unstarted
Console.WriteLine(thread.ThreadState);
thread.Start();
//線程thread啓動後,線程的狀態變爲:Running
Console.WriteLine(thread.ThreadState);
Thread.Sleep(1000);
//線程thread此時執行到Thread.Sleep(2000);,狀態爲:WaitSleepJoin
Console.WriteLine(thread.ThreadState);
thread.Join();
//線程被執行了Join導致此處代碼被阻塞,這裏獲取的狀態肯定是Stopped
Console.WriteLine(thread.ThreadState);
//主線程狀態也從WaitSleepJoin改爲Running
Console.WriteLine("主線程:" + threadMain.ThreadState);
}
}
}
輸出效果:
2.2 測試Abort()方法終止線程
.netcore不支持Abort,而且線程的正確終止方法應該是通過信號燈讓線程本身結束,而不是強制結束,因爲你無法預測線程正在做什麼!
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
Test5();
Console.WriteLine("ok");
Console.ReadLine();
}
private static void Test5()
{
var thread = new Thread(() =>
{
try
{
Thread.Sleep(2000);
}
catch (Exception ex)
{
Console.WriteLine("異常:" + ex.Message);
}
for (int i = 0; i < 10000; i++)
{
File.AppendAllText("d:\\temp.txt", i + "\r\n");
}
});
//線程thread剛被創建,還未啓動,狀態爲:Unstarted
Console.WriteLine(thread.ThreadState);
thread.Start();
//線程thread啓動後,線程的狀態變爲:Running
Console.WriteLine(thread.ThreadState);
Thread.Sleep(1000);
//線程thread此時執行了Thread.Sleep(2000);,狀態爲:WaitSleepJoin
Console.WriteLine(thread.ThreadState);
thread.Abort();
Thread.Sleep(500);
//線程thread已經Aborted了
Console.WriteLine(thread.ThreadState);
}
}
}
輸出效果:
2.3 測試線程的掛起和休眠的疊加狀態: 先從休眠中喚醒再解除掛起
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
Test4();
Console.WriteLine("ok");
Console.ReadLine();
}
private static void Test4()
{
var thread = new Thread(() =>
{
try
{
Thread.Sleep(2000);
}
catch (Exception ex)
{
Console.WriteLine("異常:" + ex.Message);
}
for (int i = 0; i < 1000; i++)
{
File.AppendAllText("d:\\temp.txt", i + "\r\n");
}
});
//線程thread剛被創建,還未啓動,狀態爲:Unstarted
Console.WriteLine(thread.ThreadState);
thread.Start();
//線程thread啓動後,線程的狀態變爲:Running
Console.WriteLine(thread.ThreadState);
Thread.Sleep(1000);
//線程thread此時執行了Thread.Sleep(2000);,狀態爲:WaitSleepJoin
Console.WriteLine(thread.ThreadState);
thread.Suspend();
Thread.Sleep(500);
//線程thread被執行Suspend(),狀態爲SuspendRequested, WaitSleepJoin,並引發異常
Console.WriteLine(thread.ThreadState);
if (thread.ThreadState == (ThreadState.SuspendRequested | ThreadState.WaitSleepJoin))
{
Console.WriteLine("ThreadState.SuspendRequested | ThreadState.WaitSleepJoin\t" + true);
}
Thread.Sleep(1500);
//線程thread此時已從Sleep中甦醒,狀態爲Suspend
Console.WriteLine(thread.ThreadState);
thread.Resume();
Thread.Sleep(500);
//線程thread被執行Resume(),狀態爲Running
Console.WriteLine(thread.ThreadState);
Thread.Sleep(10000);
//線程thread已經執行完畢,狀態爲Stopped
Console.WriteLine(thread.ThreadState);
}
}
}
輸出效果:
2.4 測試線程的掛起和休眠的疊加狀態: 先解除掛起再從休眠中喚醒
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
Test3();
Console.WriteLine("ok");
Console.ReadLine();
}
/// <summary>
/// 測試線程狀態Unstarted->Running->WaitSleepJoin->SuspendRequested, WaitSleepJoin->WaitSleepJoin->Running->Stopped
/// 線程先睡眠再掛起,然後解除掛起,然後自動喚醒
/// .netcore不支持Suspend/Resume,而且這兩個方法已經標記爲廢棄的api
/// </summary>
private static void Test3()
{
var thread = new Thread(() =>
{
try
{
Thread.Sleep(2000);
}
catch (Exception ex)
{
Console.WriteLine("異常:" + ex.Message);
}
for (int i = 0; i < 1000; i++)
{
File.AppendAllText("d:\\temp.txt", i + "\r\n");
}
});
//線程thread剛被創建,還未啓動,狀態爲:Unstarted
Console.WriteLine(thread.ThreadState);
thread.Start();
//線程thread啓動後,線程的狀態變爲:Running
Console.WriteLine(thread.ThreadState);
Thread.Sleep(1000);
//線程thread此時執行了Thread.Sleep(2000);,狀態爲:WaitSleepJoin
Console.WriteLine(thread.ThreadState);
thread.Suspend();
Thread.Sleep(500);
//線程thread被執行Suspend(),狀態爲SuspendRequested, WaitSleepJoin
Console.WriteLine(thread.ThreadState);
thread.Resume();
Thread.Sleep(500);
//線程thread被執行Resume(),狀態爲WaitSleepJoin
Console.WriteLine(thread.ThreadState);
Thread.Sleep(2000);
//線程thread已經從Sleep中甦醒,狀態爲Running
Console.WriteLine(thread.ThreadState);
Thread.Sleep(8000);
//線程thread已經執行完畢,狀態爲Stopped
Console.WriteLine(thread.ThreadState);
}
}
}
輸出效果:
2.5 測試線程休眠後使用Interrupt方法喚醒
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
Test2();
Console.WriteLine("ok");
Console.ReadLine();
}
/// <summary>
/// 測試線程狀態Unstarted->Running->WaitSleepJoin->Running->Stopped
/// 休眠後使用Interrupt方法喚醒
/// </summary>
private static void Test2()
{
var thread = new Thread(() =>
{
try
{
Thread.Sleep(2000);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
for (int i = 0; i < 1000; i++)
{
File.AppendAllText("d:\\temp.txt", i + "\r\n");
}
});
//線程thread剛被創建,還未啓動,狀態爲:Unstarted
Console.WriteLine(thread.ThreadState);
thread.Start();
//線程thread啓動後,線程的狀態變爲:Running
Console.WriteLine(thread.ThreadState);
Thread.Sleep(1000);
//線程thread此時執行了Thread.Sleep(2000);,狀態爲:WaitSleepJoin
Console.WriteLine(thread.ThreadState);
thread.Interrupt();
Thread.Sleep(100);
//線程thread被執行Interrupt(),狀態爲Running
Console.WriteLine(thread.ThreadState);
Thread.Sleep(10 * 1000);
//線程thread已經執行完畢,狀態爲Stopped
Console.WriteLine(thread.ThreadState);
}
}
}
輸出效果:
2.6 測試線程休眠後自動喚醒
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
Test1();
Console.WriteLine("ok");
Console.ReadLine();
}
/// <summary>
/// 測試線程狀態Unstarted->Running->WaitSleepJoin->Running->Stopped
/// 休眠到期後自動喚醒
/// </summary>
private static void Test1()
{
var thread = new Thread(() =>
{
Thread.Sleep(2000);
for (int i = 0; i < 1000; i++)
{
File.AppendAllText("d:\\temp.txt", i + "\r\n");
}
});
//線程thread剛被創建,還未啓動,狀態爲:Unstarted
Console.WriteLine(thread.ThreadState);
thread.Start();
//線程thread啓動後,線程的狀態變爲:Running
Console.WriteLine(thread.ThreadState);
Thread.Sleep(1000);
//線程thread此時執行了Thread.Sleep(2000);,狀態爲:WaitSleepJoin
Console.WriteLine(thread.ThreadState);
Thread.Sleep(1500);
//線程thread此時正在運行,狀態爲Running
Console.WriteLine(thread.ThreadState);
Thread.Sleep(8000);
//線程thread此時已經運行完畢,狀態爲Stopped
Console.WriteLine(thread.ThreadState);
}
}
}
輸出效果: