多線程編程是 C# 一個比較難且涵蓋面比較廣的知識點,本文整理倉促而且屬于筆記向博客,有些地方必然還存在不嚴謹和錯誤,本人會在日后的使用過程中不斷完善。如果發(fā)現(xiàn)問題或有改進意見可以在評論區(qū)提出,我會及時修改。
C# 多線程
線程是程序的執(zhí)行流程,也被稱為輕量級進程。一個進程中可以包含多個線程,每個線程有自己獨立的運行堆棧和程序計數(shù)器,可以獨立地執(zhí)行不同的任務(wù)。
我們編寫的程序到目前為止只有一個線程,作為應(yīng)用程序的運行實例,也就是說它是一個單一的進程在運行。然而,這種方式只能同時執(zhí)行一個任務(wù),限制了應(yīng)用程序的并發(fā)性能。
為了提高應(yīng)用程序的效率,我們可以使用線程。通過將程序分為多個線程,可以同時處理多個任務(wù),從而減少等待時間和提高程序的響應(yīng)速度。線程還可以實現(xiàn)并發(fā)編程,充分利用多核處理器的性能,提高應(yīng)用程序的運行效率。
線程的應(yīng)用非常廣泛,包括但不限于操作系統(tǒng)、Web服務(wù)器、數(shù)據(jù)庫、游戲等。
1、線程的生命周期
線程生命周期開始于 System.Threading.Thread
類的對象被創(chuàng)建時,結(jié)束于線程被終止或完成執(zhí)行時。下面列出了線程生命周期中的各種狀態(tài):
-
創(chuàng)建:使用
System.Threading
命名空間中的Thread
類創(chuàng)建線程實例。 - 就緒:線程實例被創(chuàng)建后,它進入就緒狀態(tài)。在此狀態(tài)下,操作系統(tǒng)為線程分配必要的資源,但線程實際上還沒有開始執(zhí)行。
-
運行:一旦調(diào)用線程的
Start()
方法,線程就會開始執(zhí)行,并處于運行狀態(tài)。在此狀態(tài)下,線程開始執(zhí)行它的方法體中的代碼。 - 阻塞:線程可能會因為等待某些資源而被阻塞,例如等待用戶輸入、等待其他線程完成任務(wù)等。在此狀態(tài)下,線程暫時停止執(zhí)行,并將 CPU 時間片讓給其他線程。
- 終止:當線程完成它的任務(wù)、遇到異?;虮粡娭平K止時,線程將終止。
2、線程的創(chuàng)建與管理
為了創(chuàng)建和控制線程,我們需要使用 System.Threading
命名空間,該命名空間提供了 Thread
、ThreadPool
、Timer
等等類和接口。通過這些類和接口,我們可以實現(xiàn)多線程編程的核心功能,例如創(chuàng)建和啟動線程,管理線程的狀態(tài)和優(yōu)先級,控制線程同步和互斥,以及使用定時器來調(diào)度線程的執(zhí)行。
2.1 線程的創(chuàng)建
C# 在 4.0 以后一共有3種創(chuàng)建線程的方式,交給線程執(zhí)行的方法通過委托傳遞。
-
Thread 類:
Thread 類是 C# 中最基本的創(chuàng)建線程的方式。在使用 Thread 類時,需要創(chuàng)建一個 Thread 對象,并將要執(zhí)行的方法包裝在一個 ThreadStart 委托中。然后,可以調(diào)用 Thread 對象的 Start 方法來啟動線程。
以下是使用 Thread 類創(chuàng)建線程的示例代碼:
using System;
using System.Threading;
class Program
{
static void Main()
{
Thread thread = new Thread(new ThreadStart(DoWork));
thread.Start();
// 主線程繼續(xù)執(zhí)行其他操作
}
static void DoWork()
{
// 線程要執(zhí)行的任務(wù)
}
}
-
ThreadPool 類:
ThreadPool 類是 C# 中一個內(nèi)置的線程池,可以用于管理和復(fù)用多個線程,從而避免頻繁地創(chuàng)建和銷毀線程,提高程序的性能和效率。在使用 ThreadPool 類時,需要將要執(zhí)行的方法包裝在一個 WaitCallback 委托中,并通過 ThreadPool.QueueUserWorkItem 方法將該委托添加到線程池中。
以下是使用 ThreadPool 類創(chuàng)建線程的示例代碼:
using System;
using System.Threading;
class Program
{
static void Main()
{
ThreadPool.QueueUserWorkItem(new WaitCallback(DoWork));
// 主線程繼續(xù)執(zhí)行其他操作
}
static void DoWork(object state)
{
// 線程要執(zhí)行的任務(wù)
}
}
-
Task 類:
Task 類是 C# 4.0 中引入的一種新的線程創(chuàng)建方式,使用 Task 類可以方便地創(chuàng)建異步任務(wù),避免手動管理線程和線程池。在使用 Task 類時,需要創(chuàng)建一個 Task 對象,并將要執(zhí)行的方法包裝在一個 Action 委托中。然后,可以調(diào)用 Task 對象的 Start 或 Run 方法來啟動線程。
以下是使用 Task 類創(chuàng)建線程的示例代碼:
using System;
using System.Threading.Tasks;
class Program
{
static void Main()
{
Task task = new Task(new Action(DoWork));
task.Start();
// 主線程繼續(xù)執(zhí)行其他操作
}
static void DoWork()
{
// 線程要執(zhí)行的任務(wù)
}
}
如果使用 Task.Factory.StartNew()
方法創(chuàng)建任務(wù),不需要調(diào)用 Start()
方法,因為任務(wù)會在創(chuàng)建后立即啟動執(zhí)行。例如:
Task myTask = Task.Factory.StartNew(DoWork);
創(chuàng)建線程時其實還有簡潔寫法,可以不需要在被執(zhí)行方法外部使用 new 委托類型()
來包裝。這是因為當被執(zhí)行方法符合對應(yīng)委托類型的簽名時,編譯器會隱式地將方法轉(zhuǎn)換為委托。下面以Thread 類舉例說明:
- 使用無參方法,可直接省略:
Thread t = new Thread(MyMethod);
t.Start();
- 使用有參方法,利用無參的 lambda 表達式(在代碼塊內(nèi)傳參):
Thread t = new Thread(() => MyMethodWithArgs(arg1, arg2));
t.Start();
- 使用有參方法,利用無參的匿名方法(在代碼塊內(nèi)傳參):
Thread t = new Thread(delegate() { MyMethodWithArgs(arg1, arg2); });
t.Start();
2.2 線程的管理
線程的管理涉及到 System.Threading
命名空間下相關(guān)類的方法和屬性的使用,這里簡單舉例,在后面介紹相關(guān)類的時候進一步介紹。
-
Thread.Start()
方法:啟動線程。 -
Thread.Join()
方法:等待線程執(zhí)行完畢。 -
Thread.Sleep()
方法:暫停當前線程的執(zhí)行一段時間。 -
Thread.Abort()
方法:強制終止線程的執(zhí)行。 -
ThreadState
屬性:表示線程的不同狀態(tài)。
2.3 多線程實例
以下是一個簡單的多線程編碼實例,它創(chuàng)建了兩個線程并在每個線程中執(zhí)行不同的函數(shù),以便同時執(zhí)行多個任務(wù):
using System;
using System.Threading;
class Program
{
static void Main()
{
// 創(chuàng)建第一個線程并啟動
Thread thread1 = new Thread(new ThreadStart(DoTask1));
/* Thread thread1 = new Thread(DoTask1); */
thread1.Start();
// 創(chuàng)建第二個線程并啟動
Thread thread2 = new Thread(new ThreadStart(DoTask2));
thread2.Start();
// 等待線程結(jié)束
thread1.Join();
thread2.Join();
Console.WriteLine("所有任務(wù)已完成");
}
static void DoTask1()
{
// 執(zhí)行任務(wù)1的代碼
Console.WriteLine("任務(wù)1開始執(zhí)行...");
Thread.Sleep(3000);
Console.WriteLine("任務(wù)1執(zhí)行完成");
}
static void DoTask2()
{
// 執(zhí)行任務(wù)2的代碼
Console.WriteLine("任務(wù)2開始執(zhí)行...");
Thread.Sleep(5000);
Console.WriteLine("任務(wù)2執(zhí)行完成");
}
}
在這個例子中,我們使用 Thread
類來創(chuàng)建和管理線程。我們創(chuàng)建了兩個線程,每個線程都調(diào)用不同的函數(shù)(DoTask1
和 DoTask2
)來執(zhí)行不同的任務(wù)。我們使用 Thread.Sleep()
方法來模擬每個任務(wù)的耗時,并使用 Join()
方法來等待每個線程的結(jié)束。最后,我們在主函數(shù)中輸出一條消息來表示所有任務(wù)已完成。
3、Thread 類
Thread
類是 .NET 提供的用于創(chuàng)建和控制線程的基本類。使用 Thread
類可以在應(yīng)用程序中創(chuàng)建新線程,同時控制線程的狀態(tài)和執(zhí)行。
下表列出了 Thread
類的一些常用的屬性:
序號 | 屬性 | 類型 | 描述 |
---|---|---|---|
1 | CurrentContext | System.Runtime.Remoting.Contexts.Context | 獲取當前線程正在執(zhí)行的上下文。上下文是一個邏輯容器,它定義了一個對象的代碼執(zhí)行上下文,包括對象所運行的代碼、對象的安全策略和上下文中的屬性。 |
2 | CurrentCulture | System.Globalization.CultureInfo | 用于確定在將數(shù)據(jù)類型轉(zhuǎn)換為字符串時要使用的區(qū)域性。它主要影響數(shù)字和日期時間格式,以及貨幣和浮點數(shù)值的小數(shù)點和千位分隔符。 可以通過在程序中顯式設(shè)置此屬性來更改線程的當前區(qū)域性。 |
3 | CurrentPrincipal | System.Security.Principal.IPrincipal | 在 Windows 操作系統(tǒng)中,用戶和組都有關(guān)聯(lián)的安全令牌,這些安全令牌用于授予或拒絕對某些資源的訪問權(quán)限。CurrentPrincipal 屬性可用于檢查當前線程的安全令牌,以確定線程是否有訪問某個資源的權(quán)限。 可以使用該屬性來更改當前線程的負責人,以便在運行時更改線程的權(quán)限。 |
4 | CurrentThread | System.Threading.Thread | 獲取當前線程的 Thread 對象。 |
5 | CurrentUICulture | System.Globalization.CultureInfo | 用于確定在運行時加載資源文件時使用的區(qū)域性。該屬性主要用于確定應(yīng)使用哪個語言和文化特性來顯示用戶界面(UI)的文本,如按鈕、菜單、對話框等。 可以在程序中顯式設(shè)置此屬性來更改線程的當前 UI 區(qū)域性。 |
6 | ExecutionContext | System.Threading.ExecutionContext | 獲取一個 ExecutionContext 對象,該對象包含有關(guān)當前線程的各種上下文的信息。這個對象可以被用來在多個線程之間傳遞上下文信息。 |
7 | IsAlive | bool | 判斷線程是否處于活動狀態(tài)。如果線程已經(jīng)啟動并正在運行,則返回 true,否則返回 false。 |
8 | IsBackground | bool | 獲取或設(shè)置一個值,該值指示某個線程是否為后臺線程。 |
9 | IsThreadPoolThread | bool | 獲取一個值,該值指示線程是否屬于托管線程池。 |
10 | ManagedThreadId | int | 獲取當前托管線程的唯一標識符。 |
11 | Name | string | 獲取或設(shè)置線程的名稱。 |
12 | Priority | System.Threading.ThreadPriority | 獲取或設(shè)置一個值,該值指示線程的調(diào)度優(yōu)先級。 |
13 | ThreadState | System.Threading.ThreadState | 獲取線程的當前狀態(tài),它是一個枚舉類型的值,包括 Unstarted 、Running 、Stopped 、WaitSleepJoin 等狀態(tài)。 |
以下是一個 Thread
類使用上述提到的部分屬性的示例:
using System;
using System.Globalization;
using System.Threading;
class Program
{
static void Main()
{
// 獲取當前線程
Thread currentThread = Thread.CurrentThread;
// 設(shè)置線程名稱
currentThread.Name = "MyThread";
// 獲取和設(shè)置線程優(yōu)先級
currentThread.Priority = ThreadPriority.Highest;
// 獲取線程的唯一標識符
int threadId = currentThread.ManagedThreadId;
Console.WriteLine($"Thread ID: {threadId}");
// 獲取和設(shè)置線程的后臺標志
currentThread.IsBackground = true;
// 獲取線程狀態(tài)
ThreadState threadState = currentThread.ThreadState;
Console.WriteLine($"Thread state: {threadState}");
// 獲取和設(shè)置線程的區(qū)域性
CultureInfo cultureInfo = new CultureInfo("en-US");
currentThread.CurrentCulture = cultureInfo;
// 獲取和設(shè)置線程的 UI 區(qū)域性
CultureInfo uiCultureInfo = new CultureInfo("fr-FR");
currentThread.CurrentUICulture = uiCultureInfo;
// 獲取和設(shè)置線程的當前負責人(對基于角色的安全性而言)
AppDomain.CurrentDomain.SetPrincipalPolicy(System.Security.Principal.PrincipalPolicy.WindowsPrincipal);
Thread.CurrentPrincipal = new System.Security.Principal.WindowsPrincipal(System.Security.Principal.WindowsIdentity.GetCurrent());
// 獲取和設(shè)置線程的 ExecutionContext 對象
ExecutionContext executionContext = ExecutionContext.Capture();
currentThread.ExecutionContext = executionContext;
// 輸出線程信息
Console.WriteLine($"Thread name: {currentThread.Name}");
Console.WriteLine($"Thread priority: {currentThread.Priority}");
Console.WriteLine($"Thread background: {currentThread.IsBackground}");
Console.WriteLine($"Thread culture: {currentThread.CurrentCulture}");
Console.WriteLine($"Thread UI culture: {currentThread.CurrentUICulture}");
Console.WriteLine($"Thread principal: {Thread.CurrentPrincipal}");
Console.WriteLine($"Thread execution context: {currentThread.ExecutionContext}");
}
}
這個示例代碼演示了如何使用 Thread
類的各種實例屬性。它創(chuàng)建了一個新的線程,將線程的名稱設(shè)置為 "MyThread"
,優(yōu)先級設(shè)置為最高,將線程設(shè)置為后臺線程,并獲取了線程的唯一標識符和狀態(tài)。此外,示例還演示了如何獲取和設(shè)置線程的區(qū)域性、UI 區(qū)域性、當前負責人和 ExecutionContext 對象。最后,示例輸出了所有線程信息。
下表列出了 Thread
類的一些常用的方法:
序號 | 方法名 | 描述 |
---|---|---|
1 | public void Abort()
|
在調(diào)用此方法的線程上引發(fā) ThreadAbortException 異常,以開始終止此線程的過程。調(diào)用此方法通常會銷毀線程,并觸發(fā) finally 塊。 |
2 | public static LocalDataStoreSlot AllocateDataSlot()
|
在所有的線程上分配未命名的數(shù)據(jù)槽。為了獲得更好的性能,請改用以 ThreadStaticAttribute 屬性標記的字段。 |
3 | public static LocalDataStoreSlot AllocateNamedDataSlot( string name)
|
在所有線程上分配已命名的數(shù)據(jù)槽。為了獲得更好的性能,請改用以 ThreadStaticAttribute 屬性標記的字段。 |
4 | public static void BeginCriticalRegion()
|
通知主機執(zhí)行將要進入一個代碼區(qū)域,在該代碼區(qū)域內(nèi)線程中止或未經(jīng)處理的異常的影響可能會危害應(yīng)用程序域中的其他任務(wù)。 |
5 | public static void BeginThreadAffinity()
|
通知主機托管代碼將要執(zhí)行依賴于當前物理操作系統(tǒng)線程的標識的指令。 |
6 | public static void EndCriticalRegion()
|
通知主機執(zhí)行將要進入一個代碼區(qū)域,在該代碼區(qū)域內(nèi)線程中止或未經(jīng)處理的異常僅影響當前任務(wù)。 |
7 | public static void EndThreadAffinity()
|
通知主機托管代碼已執(zhí)行完依賴于當前物理操作系統(tǒng)線程的標識的指令。 |
8 | public static void FreeNamedDataSlot(string name)
|
為進程中的所有線程消除名稱與槽之間的關(guān)聯(lián)。為了獲得更好的性能,請改用以 ThreadStaticAttribute 屬性標記的字段。 |
9 | public static Object GetData( LocalDataStoreSlot slot )
|
在當前線程的當前域中從當前線程上指定的槽中檢索值。為了獲得更好的性能,請改用以 ThreadStaticAttribute 屬性標記的字段。 |
10 | public static AppDomain GetDomain()
|
返回當前線程正在其中運行的當前域。 |
11 | public static AppDomain GetDomainID()
|
返回唯一的應(yīng)用程序域標識符。 |
12 | public static LocalDataStoreSlot GetNamedDataSlot( string name )
|
查找已命名的數(shù)據(jù)槽。為了獲得更好的性能,請改用以 ThreadStaticAttribute 屬性標記的字段。 |
13 | public void Interrupt()
|
中斷處于 WaitSleepJoin 線程狀態(tài)的線程。 |
14 | public void Join()
|
在繼續(xù)執(zhí)行標準的 COM 和 SendMessage 消息泵處理期間,阻塞調(diào)用線程,直到某個線程終止為止。此方法有不同的重載形式。 |
15 | public static void MemoryBarrier()
|
同步內(nèi)存存取:執(zhí)行當前線程的處理器在對指令重新排序時,不能采用先執(zhí)行 MemoryBarrier 調(diào)用之后的內(nèi)存存取,再執(zhí)行 MemoryBarrier 調(diào)用之前的內(nèi)存存取的方式。 |
16 | public static void ResetAbort()
|
取消為當前線程請求的 Abort。 |
17 | public void Resume()
|
恢復(fù)掛起的線程。(已和 Suspend 一同被廢棄) |
18 | public static void SetData(LocalDataStoreSlot slot, Object data)
|
在當前正在運行的線程上為此線程的當前域在指定槽中設(shè)置數(shù)據(jù)。為了獲得更好的性能,請改用以 ThreadStaticAttribute 屬性標記的字段。 |
19 | public void Start()
|
開始一個線程。 |
20 | public static void Sleep(int millisecondsTimeout)
|
讓線程暫停一段時間,注意時間單位是毫秒。 |
21 | public void Suspend()
|
掛起線程。(已被廢棄,因為容易導致死鎖和應(yīng)用程序崩潰) |
22 | public static void SpinWait(int iterations)
|
導致線程等待由 iterations 參數(shù)定義的時間量。 |
23 | public static byte VolatileRead(ref byte address) public static double VolatileRead(ref double address) public static int VolatileRead(ref int address) public static Object VolatileRead(ref Object address)
|
讀取字段值。無論處理器的數(shù)目或處理器緩存的狀態(tài)如何,該值都是由計算機的任何處理器寫入的最新值。此方法有不同的重載形式,這里只給出了一些形式。 |
24 | public static void VolatileWrite(ref byte address, byte value) public static void VolatileWrite(ref double address, double value) public static void VolatileWrite(ref int address, int value) public static void VolatileWrite(ref Object address, Object value)
|
立即向字段寫入一個值,以使該值對計算機中的所有處理器都可見。此方法有不同的重載形式。這里只給出了一些形式。 |
25 | public static bool Yield()
|
導致調(diào)用線程執(zhí)行準備好在當前處理器上運行的另一個線程。由操作系統(tǒng)選擇要執(zhí)行的線程。 |
下面是一個 Thread
類使用上述提到的部分方法和屬性的示例代碼:
using System;
using System.Threading;
public class Example
{
public static void Main()
{
// 創(chuàng)建一個新的線程,并指定線程函數(shù)
Thread t = new Thread(new ThreadStart(ThreadFunction));
t.Name = "MyThread";
t.Priority = ThreadPriority.Highest;
// 啟動線程
t.Start();
// 當前線程等待子線程完成
t.Join();
// 給子線程發(fā)送中斷信號
t.Interrupt();
// 判斷線程是否仍在運行
if (t.IsAlive)
{
Console.WriteLine("Thread is still running.");
}
/* 掛起線程
t.Suspend();
Console.WriteLine("Thread is suspended."); */
/* 恢復(fù)線程
t.Resume();
Console.WriteLine("Thread is resumed."); */
// 終止線程
t.Abort();
}
public static void ThreadFunction()
{
try
{
// 休眠 2 秒
Thread.Sleep(2000);
// 輸出線程名稱和優(yōu)先級
Console.WriteLine("Thread name: {0}", Thread.CurrentThread.Name);
Console.WriteLine("Thread priority: {0}", Thread.CurrentThread.Priority);
}
catch (ThreadInterruptedException e)
{
Console.WriteLine(e);
}
}
}
這個示例代碼創(chuàng)建了一個新的線程,并指定了一個線程函數(shù) ThreadFunction
。
4、ThreadPool 類
ThreadPool
類是 C# 中用于管理線程池的類。它可以使程序員更方便地管理多個線程,而無需手動創(chuàng)建和銷毀線程。
-
線程池(ThreadPool)是一種可重用的線程集合,它可以執(zhí)行多個任務(wù),而不必在每個任務(wù)完成后啟動新線程。
-
線程池在應(yīng)用程序啟動時會創(chuàng)建一定數(shù)量的線程,并將它們保存在線程池中,這些線程稱為工作線程。當需要執(zhí)行任務(wù)時,應(yīng)用程序會將任務(wù)添加到線程池的隊列中,線程池會自動分配空閑的工作線程來執(zhí)行這些任務(wù)。當任務(wù)完成后,工作線程會返回到線程池中,等待下一個任務(wù)的分配。
-
使用線程池可以避免頻繁地創(chuàng)建和銷毀線程,從而減少系統(tǒng)開銷和資源浪費。線程池還可以控制同時執(zhí)行的線程數(shù),防止系統(tǒng)過載,提高應(yīng)用程序的穩(wěn)定性和可靠性。
下表列出了 ThreadPool
類的一些常用的屬性:
常用屬性
序號 | 屬性 | 類型 | 描述 |
---|---|---|---|
1 | AvailableThreads | int | 獲取可用于執(zhí)行工作項的空閑工作線程數(shù) |
2 | CompletedWorkItemCount | int | 獲取已完成的工作項數(shù)量 |
3 | IsThreadPoolThread | bool | 獲取當前線程是否屬于線程池 |
4 | MaxThreads | int | 獲取或設(shè)置線程池可同時擁有的最大工作線程數(shù)。 |
5 | MinThreads | int | 獲取或設(shè)置線程池可同時擁有的最小工作線程數(shù)。 |
6 | PendingWorkItemCount | int | 獲取等待執(zhí)行的工作項數(shù)量 |
7 | ThreadCount | int | 獲取當前正在運行的線程池中的工作線程數(shù) |
下表列出了 ThreadPool
類的一些常用的方法:
序號 | 方法名 | 描述 |
---|---|---|
1 | public static bool QueueUserWorkItem(WaitCallback callBack)
|
將方法排隊到線程池的工作隊列中,等待執(zhí)行。 |
2 | public static bool QueueUserWorkItem(WaitCallback callBack, object state)
|
將帶有狀態(tài)信息的工作項排入線程池的工作隊列中,等待執(zhí)行。 |
3 | public static void GetMaxThreads(out int workerThreads, out int completionPortThreads)
|
檢索可同時處于活動狀態(tài)的線程數(shù)的最大值。 |
4 | public static void GetMinThreads(out int workerThreads, out int completionPortThreads)
|
檢索線程池允許的最小線程數(shù)。 |
5 | public static bool SetMaxThreads(int workerThreads, int completionPortThreads)
|
設(shè)置線程池中允許的最大線程數(shù)和異步 I/O 線程數(shù)。 |
6 | static bool SetMinThreads(int workerThreads, int completionPortThreads)
|
設(shè)置線程池中允許的最小線程數(shù)和異步 I/O 線程數(shù)。 |
7 | public static bool UnsafeQueueNativeOverlapped(NativeOverlapped* overlapped, IOCompletionCallback iocb, object state)
|
使用指定的回調(diào)函數(shù)將 NativeOverlapped 結(jié)構(gòu)排隊到線程池以便進行異步 I/O 操作。 |
8 | public static bool UnsafeQueueUserWorkItem(WaitCallback callBack, object state)
|
將工作項排入線程池的工作隊列中等待執(zhí)行,但不強制線程池線程在調(diào)用方法之前獲得安全上下文。 |
下面是一個示例代碼:
using System;
using System.Threading;
class Program
{
static void Main()
{
// 獲取當前線程池的最大線程數(shù)和空閑線程數(shù)
int maxThreads, availableThreads;
ThreadPool.GetMaxThreads(out maxThreads, out _); // 獲取最大線程數(shù)
ThreadPool.GetAvailableThreads(out availableThreads, out _); // 獲取可用線程數(shù)
Console.WriteLine($"MaxThreads: {maxThreads}, AvailableThreads: {availableThreads}");
// 提交工作項到線程池
ThreadPool.QueueUserWorkItem(WorkItemCallback, "Hello, world!"); // 將工作項提交到線程池中
Console.WriteLine("Work item submitted to thread pool");
// 等待工作項完成
EventWaitHandle waitHandle = new ManualResetEvent(false); // 創(chuàng)建一個事件等待句柄
ThreadPool.QueueUserWorkItem(_ => {
Console.WriteLine("Working on a task...");
Thread.Sleep(1000); // 模擬長時間運行的工作項
Console.WriteLine("Task completed!");
waitHandle.Set(); // 通知等待句柄任務(wù)已完成
});
waitHandle.WaitOne(); // 等待等待句柄收到通知
Console.WriteLine("Task has finished");
// 等待所有工作項完成
ManualResetEvent allTasksCompleted = new ManualResetEvent(false); // 創(chuàng)建一個事件等待句柄
for (int i = 0; i < 10; i++)
{
ThreadPool.QueueUserWorkItem(_ => {
Console.WriteLine($"Working on task {i}...");
Thread.Sleep(1000); // 模擬長時間運行的工作項
Console.WriteLine($"Task {i} completed!");
if (Interlocked.Decrement(ref remainingTasks) == 0) // 如果剩余任務(wù)數(shù)量為0,則通知所有任務(wù)已完成
{
allTasksCompleted.Set();
}
});
}
allTasksCompleted.WaitOne(); // 等待所有任務(wù)完成
Console.WriteLine("All tasks have completed");
// 等待線程池中的所有線程退出
ThreadPool.SetMaxThreads(0, 0); // 設(shè)置最大線程數(shù)為0
ThreadPool.SetMinThreads(0, 0); // 設(shè)置最小線程數(shù)為0
while (ThreadPool.ThreadCount > 0) // 循環(huán)等待線程池中的線程全部退出
{
Console.WriteLine($"Waiting for {ThreadPool.ThreadCount} threads to exit...");
Thread.Sleep(1000);
}
Console.WriteLine("All threads have exited");
}
private static int remainingTasks = 10; // 剩余任務(wù)數(shù)量
private static void WorkItemCallback(object state)
{
Console.WriteLine($"Working on {state}...");
Thread.Sleep(1000); // 模擬長時間運行的工作項
Console.WriteLine($"{state} completed!");
}
}
上述代碼存在 QueueUserWorkItem
方法與 lambda 表達式的結(jié)合使用:
- 我們通過 lambda 表達式聲明了一個匿名函數(shù),作為參數(shù)傳遞給
QueueUserWorkItem
方法,其中_
是一個占位符,表示 lambda 表達式不需要任何參數(shù)。
ThreadPool.QueueUserWorkItem( _ => {
Console.WriteLine("Working on a task...");
Thread.Sleep(1000); // 模擬長時間運行的工作項
Console.WriteLine("Task completed!");
waitHandle.Set(); // 通知等待句柄任務(wù)已完成
});
5、Task 類
Task
類是 .NET Framework 中用于支持多線程編程的類之一。它提供了一種異步編程模型,可以在一個線程中執(zhí)行多個操作,從而提高程序的并發(fā)性和效率。Task 類的實例代表一個異步操作,可以使用 Task 對象來監(jiān)視和控制異步操作的執(zhí)行過程。
-
Task
類是泛型類,它可以實例化出具有特定類型返回值的任務(wù)。如果不指定類型參數(shù),會使用Task<object>
作為默認類型。 -
Task
類的實例代表一個異步操作,例如一個 I/O 操作或者一個計算密集型的操作。 - 可以使用 Task 對象來監(jiān)視和控制異步操作的執(zhí)行過程。在異步執(zhí)行期間,我們可以通過輪詢 Task 對象來檢查操作的進度,或者在操作完成時通過 Task 對象來獲取操作的結(jié)果。
下表列出了 ThreadPool
類的一些常用的屬性:
序號 | 屬性 | 類型 | 描述 |
---|---|---|---|
1 | Id | int | 獲取任務(wù)的唯一標識符。 |
2 | Status | TaskStatus | 獲取任務(wù)的當前執(zhí)行狀態(tài)。 |
3 | Exception | Exception | 獲取表示任務(wù)執(zhí)行期間引發(fā)的任何異常。 |
4 | CreationOptions | TaskCreationOptions | 獲取任務(wù)創(chuàng)建時使用的 TaskCreationOptions。 |
5 | AsyncState | Object | 獲取任務(wù)關(guān)聯(lián)的異步狀態(tài)對象。 |
6 | IsCanceled | bool | 獲取一個值,該值指示任務(wù)是否已被取消。 |
7 | IsCompleted | bool | 獲取一個值,該值指示任務(wù)是否已完成執(zhí)行。 |
8 | IsFaulted | bool | 獲取一個值,該值指示任務(wù)是否已發(fā)生故障。 |
9 | Factory | TaskFactory | 獲取用于創(chuàng)建和調(diào)度此任務(wù)的 TaskFactory。 |
下表列出了 ThreadPool
類的一些常用的方法:
序號 | 方法 | 描述 |
---|---|---|
1 | public void Start()
|
啟動當前任務(wù)實例。 |
2 | public static Task Delay(int millisecondsDelay)
|
返回已啟動并在指定時間過后完成的延遲任務(wù)。 |
3 | public static Task Delay(TimeSpan delay)
|
(重載)返回已啟動并在指定時間過后完成的延遲任務(wù)。 |
4 | public static Task Run(Action action)
|
在默認任務(wù)調(diào)度程序上運行指定的操作。 |
5 | public static Task Run<TResult>(Func<TResult> function)
|
(重載)在默認任務(wù)調(diào)度程序上運行指定的函數(shù),并返回一個帶有返回值的任務(wù)對象。 |
6 | public static Task WhenAll(params Task[] tasks)
|
創(chuàng)建一個任務(wù),該任務(wù)在所有提供的任務(wù)完成時完成。 |
7 | public static Task WhenAny(params Task[] tasks)
|
(重載)創(chuàng)建一個任務(wù),該任務(wù)在任何提供的任務(wù)完成時完成。 |
8 | public void Wait()
|
阻止當前線程,直到當前任務(wù)完成。 |
9 | public bool Wait(int millisecondsTimeout)
|
(重載)阻止當前線程,直到當前任務(wù)完成或等待超時。 |
10 | public bool Wait(TimeSpan timeout)
|
(重載)阻止當前線程,直到當前任務(wù)完成或等待超時。 |
11 | public static Task FromResult<TResult>(TResult result)
|
創(chuàng)建一個已完成的任務(wù),并返回指定的結(jié)果。 |
12 | public void ContinueWith(Action<Task> continuationAction)
|
創(chuàng)建一個新的任務(wù),該任務(wù)在當前任務(wù)完成時運行指定的操作。 |
13 | public Task ContinueWith(Func<Task,?TResult> continuationFunction)
|
(重載)創(chuàng)建一個新的任務(wù),該任務(wù)在當前任務(wù)完成時運行指定的函數(shù),并返回一個帶有返回值的任務(wù)對象。 |
14 | public Task ContinueWith(Func<Task,?TResult> continuationFunction, CancellationToken cancellationToken)
|
(重載)創(chuàng)建一個新的任務(wù),該任務(wù)在當前任務(wù)完成時運行指定的函數(shù),并返回一個帶有返回值的任務(wù)對象,同時可以通過指定的 CancellationToken 請求取消操作。 |
15 | public Task ContinueWith(Func<Task,?TResult> continuationFunction, TaskContinuationOptions continuationOptions)
|
(重載)創(chuàng)建一個新的任務(wù),該任務(wù)在當前任務(wù)完成時運行指定的函數(shù),并返回一個帶有返回值的任務(wù)對象,并提供選項以控制新任務(wù)的行為。 |
在使用 Task 類創(chuàng)建線程時,可以使用 Start
方法或 Run
方法來啟動線程,二者有以下區(qū)別:
-
啟動方式:
Start
方法會在新線程上執(zhí)行任務(wù),而Run
方法則是在當前線程上同步執(zhí)行任務(wù)。 -
線程數(shù)量:
Start
方法會創(chuàng)建新線程并在其中執(zhí)行任務(wù),而Run
方法只會在當前線程上執(zhí)行任務(wù),不會創(chuàng)建新線程。 -
調(diào)用次數(shù):
Run
方法可以被多次執(zhí)行,而Start
方法只能被執(zhí)行一次,因為線程不能被重復(fù)啟動。 -
作用:
Run
方法存放任務(wù)代碼,而Start
方法同時創(chuàng)建線程并執(zhí)行任務(wù)。
需要注意的是,使用 Start
方法啟動的線程可能會在當前線程之后才開始執(zhí)行,因為它需要等待 CPU 時間片的分配。而使用 Run
方法則會立即執(zhí)行任務(wù),直到任務(wù)執(zhí)行完畢才會繼續(xù)執(zhí)行下面的代碼。
此外,使用 Task.Run
方法也可以創(chuàng)建新線程并在其中執(zhí)行任務(wù),與 Task.Start
方法類似。但是 Task.Run
方法會返回一個 Task 對象,該對象可以用于等待任務(wù)執(zhí)行完畢或者監(jiān)視任務(wù)執(zhí)行狀態(tài)。
以下是一個使用 Task
類的簡單示例代碼,該示例創(chuàng)建了一個異步任務(wù)來計算數(shù)組中所有元素的平均值:
using System;
using System.Threading.Tasks;
class Program
{
static void Main(string[] args)
{
// 定義整數(shù)數(shù)組
int[] arr = { 1, 2, 3, 4, 5 };
// 創(chuàng)建一個Task實例,計算整數(shù)數(shù)組的平均值
Task<double> task = Task.Run(() => {
double sum = 0;
foreach (int num in arr) {
sum += num;
}
return sum / arr.Length;
});
// 輸出提示信息
Console.WriteLine($"Calculating average of array...");
// 輸出Task實例返回的結(jié)果(平均值)
Console.WriteLine($"Average: {task.Result}");
}
}
在上面的代碼中,首先定義了一個整數(shù)數(shù)組 arr
,然后創(chuàng)建了一個 Task
對象,該對象使用 Task.Run()
方法執(zhí)行一個無參數(shù) Lambda 表達式。Lambda 表達式計算 arr
中所有元素的平均值,然后將結(jié)果作為 Task<double>
對象的返回值。
在執(zhí)行任務(wù)期間,Main()
方法可以繼續(xù)執(zhí)行其他操作。當需要獲取任務(wù)的結(jié)果時,可以使用 Task.Result
屬性,該屬性將等待任務(wù)完成并返回其結(jié)果。
上述代碼運行后將輸出以下內(nèi)容:
Calculating average of array…
Average: 3
需要注意的是,如果任務(wù)執(zhí)行過程中發(fā)生了異常,則可以通過 Task.Exception
屬性獲取異常信息。可以通過 Task.IsFaulted
屬性來檢查任務(wù)是否失敗。
6、Timer 類
Timer
類允許我們在一段時間間隔內(nèi)重復(fù)執(zhí)行某些代碼,可以使用 Timer
類的構(gòu)造函數(shù)創(chuàng)建計時器。
- 在初始化
Timer
實例時,可以指定一個回調(diào)函數(shù)來執(zhí)行代碼,以及第一次回調(diào)的等待時間和回調(diào)間隔時間。然后,Timer
類將在指定的時間間隔內(nèi)重復(fù)執(zhí)行回調(diào)函數(shù)。 - 除此之外,
Timer
類還有一些可用于更改回調(diào)時間或釋放資源的方法。
下表列出了 Timer
類的創(chuàng)建語法,以及一些常用的方法:
序號 | 語法格式 | 描述 |
---|---|---|
1 | public Timer(TimerCallback callback, object state, int dueTime, int period)
|
初始化一個新實例的Timer類,并設(shè)置回調(diào)方法、傳遞對象、第一次回調(diào)等待時間和回調(diào)間隔時間。 |
2 | public void Change(int dueTime, int period)
|
更改Timer的第一次回調(diào)等待時間和回調(diào)間隔時間。 |
3 | public bool Change(int dueTime, int period, bool firstTime)
|
(重載)更改Timer的第一次回調(diào)等待時間和回調(diào)間隔時間,并決定是否更改第一次回調(diào)等待時間。 |
4 | public void Change(TimeSpan dueTime, TimeSpan period)
|
(重載)更改Timer的第一次回調(diào)等待時間和回調(diào)間隔時間。 |
5 | public bool Change(TimeSpan dueTime, TimeSpan period, bool firstTime)
|
(重載)更改Timer的第一次回調(diào)等待時間和回調(diào)間隔時間,并決定是否更改第一次回調(diào)等待時間。 |
6 | public void Dispose()
|
釋放Timer類占用的所有資源。 |
7 | public bool Dispose(WaitHandle notifyObject)
|
(重載)釋放Timer類占用的所有資源,并在釋放之前等待異步操作完成。 |
8 | public static Timer Synchronized(Timer timer)
|
返回一個包裝的Timer對象,該對象在多線程環(huán)境中執(zhí)行回調(diào)方法時使用指定的Timer對象的同步上下文。 |
9 | public bool WaitForDispose(WaitHandle waitHandle)
|
等待Timer類釋放所有資源,并在等待時使用指定的WaitHandle對象。 |
下面是一個使用 Timer
類的示例代碼:
using System;
using System.Threading;
class Program
{
static void Main()
{
// 創(chuàng)建 TimerCallback 委托對象,指向 PrintTime 方法
TimerCallback callback = new TimerCallback(PrintTime);
// 創(chuàng)建 Timer 對象并啟動定時器,第一個參數(shù)是回調(diào)函數(shù),第二個參數(shù)傳遞給回調(diào)函數(shù)的狀態(tài)信息,第三個參數(shù)是時間間隔,第四個參數(shù)是定時器第一次執(zhí)行的延遲時間
Timer timer = new Timer(callback, null, TimeSpan.Zero, TimeSpan.FromSeconds(1));
// 等待用戶按下任意鍵終止定時器
Console.WriteLine("Press any key to stop the timer");
Console.ReadKey();
// 銷毀定時器
timer.Dispose();
Console.WriteLine("Timer stopped");
}
// 定義回調(diào)函數(shù) PrintTime
static void PrintTime(object state)
{
Console.WriteLine(DateTime.Now.ToString("hh:mm:ss"));
}
}
該程序使用 Timer
類每秒打印當前時間。在 Main
方法中,首先創(chuàng)建一個 TimerCallback
委托,該委托指向一個名為 PrintTime
的方法。
然后,創(chuàng)建一個 Timer
實例,并傳入 callback
和兩個 TimeSpan
參數(shù)。第一個參數(shù)表示多長時間后開始計時,TimeSpan.Zero
表示立即開始計時。第二個參數(shù)表示每隔多長時間執(zhí)行一次回調(diào)函數(shù),TimeSpan.FromSeconds(1)
表示每秒執(zhí)行一次回調(diào)函數(shù)。
在程序運行時,通過 Console.ReadKey() 暫停程序,等待用戶按下任意鍵。一旦用戶按下任意鍵,程序?qū)⑨尫?timer 并停止計時。
7、線程同步的相關(guān)類
在使用線程時,需要注意線程同步和線程安全問題,避免出現(xiàn)線程間數(shù)據(jù)競爭和死鎖等問題??梢允褂?Monitor
、Mutex
、Semaphore
等類創(chuàng)建同步對象來確保線程安全。
7.1 Monitor 類
Monitor 是 C# 中最基本的同步對象,是一個內(nèi)置的互斥鎖定對象,由 System.Threading.Monitor
類實現(xiàn)。Monitor 對象用于限制在同一時間內(nèi)只有一個線程可以訪問共享資源。
- Monitor 的主要作用是提供一個獨占鎖來確保線程安全。使得在同一時刻只有一個線程可以進入代碼塊,進而保證了代碼塊的互斥性。當一個線程進入被 Monitor 鎖定的代碼塊時,其他線程將被阻塞,直到該線程退出代碼塊。
下列是常用的一些方法:文章來源地址http://www.zghlxwxcb.cn/news/detail-658579.html
序號 | 方法 | 描述 |
---|---|---|
1 | Enter(object obj) | 獲取指定對象的互斥鎖,如果對象已經(jīng)被其他線程鎖定,則當前線程會阻塞直到對象被釋放。 |
2 | TryEnter(object obj, int timeout) | 獲取指定對象的互斥鎖,如果對象已經(jīng)被其他線程鎖定,則當前線程會阻塞,但是最多阻塞指定的超時時間,超時后該方法將返回 false。 |
3 | Exit(object obj) | 釋放指定對象的互斥鎖,如果當前線程并沒有獲取到該對象的互斥鎖,則會拋出 SynchronizationLockException 異常。 |
7.2 Mutex 類
Mutex 是一種系統(tǒng)級別的內(nèi)核對象,由 System.Threading.Mutex
類實現(xiàn)。Mutex 對象是一種可命名的同步對象,可以跨進程共享。它可以保護多個線程同時訪問共享資源,即多線程編程中的互斥鎖。
- Mutex 與 Monitor 的最大區(qū)別在于,Mutex 可以跨進程共享,而 Monitor 只能在同一個進程內(nèi)的線程間共享。Mutex 還可以防止饑餓狀態(tài)的發(fā)生,即等待較長時間但一直未被允許進入臨界區(qū)的線程不會無限期地等待下去。
下列是常用的一些方法:
序號 | 方法 | 描述 |
---|---|---|
1 | WaitOne() | 請求獲取 Mutex 對象的所有權(quán),如果該 Mutex 對象已被其他線程占用,則當前線程將被阻塞直到 Mutex 對象被釋放。 |
2 | WaitOne(int millisecondsTimeout) | 請求獲取 Mutex 對象的所有權(quán),如果該 Mutex 對象已被其他線程占用,則當前線程將被阻塞,但最多等待指定的超時時間,超時后該方法將返回 false。 |
3 | ReleaseMutex() | 釋放 Mutex 對象的所有權(quán),如果當前線程沒有獲取到該 Mutex 對象的所有權(quán),則會拋出一個異常。 |
7.3 Semaphore 類
Semaphore 也是一種同步對象,由 System.Threading.Semaphore
類實現(xiàn)。Semaphore 對象也是用于保護多個線程同時訪問一組資源,控制對某個共享資源的訪問數(shù)量。Semaphore 的使用場景包括線程池和限制并發(fā)請求等。文章來源:http://www.zghlxwxcb.cn/news/detail-658579.html
- Semaphore 類中有一個計數(shù)器,初始值表示同時可訪問該資源的線程數(shù)量。每當有一個線程訪問該資源時,計數(shù)器的值就會減一。當計數(shù)器的值為 0 時,后續(xù)訪問該資源的線程將會被阻塞,直到有一個線程釋放該資源,計數(shù)器的值才會加一。
下列是常用的一些方法:
序號 | 方法 | 描述 |
---|---|---|
1 | WaitOne() | 請求獲取 Semaphore 對象的訪問令牌,如果當前 Semaphore 對象已被其他線程占用,則當前線程將被阻塞直到 Semaphore 對象被釋放。 |
2 | WaitOne(int millisecondsTimeout) | 請求獲取 Semaphore 對象的訪問令牌,如果當前 Semaphore 對象已被其他線程占用,則當前線程將被阻塞,但最多等待指定的超時時間,超時后該方法將返回 false。 |
3 | Release() | 釋放 Semaphore 對象的一個訪問令牌,如果當前線程沒有獲取到該 Semaphore 對象的訪問令牌,則會拋出一個異常。 |
到了這里,關(guān)于unity的C#學習——多線程編程(線程的生命周期、創(chuàng)建與管理)與線程相關(guān)類的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!