C#多線程基本使用小結(jié)
線程是并發(fā)編程的基礎(chǔ)概念之一。在現(xiàn)代應(yīng)用程序中,我們通常需要執(zhí)行多個(gè)任務(wù)并行處理,以提高性能。C# 提供了多種并發(fā)編程工具,如Thread
、Task
、異步編程和Parallel
等。
Thread 類
Thread
類是最基本的線程實(shí)現(xiàn)方法。使用Thread
類,我們可以創(chuàng)建并管理獨(dú)立的線程來(lái)執(zhí)行任務(wù)。
基本使用Thread
創(chuàng)建一個(gè)新的實(shí)例對(duì)象,將一個(gè)方法直接給Thread類,并使用實(shí)例對(duì)象啟動(dòng)線程運(yùn)行。
static void Test() { Console.WriteLine("Test事件開始執(zhí)行"); Thread.Sleep(1000); Console.WriteLine("Test事件睡眠之后打印數(shù)據(jù)"); Console.WriteLine("Test事件id: " + Thread.CurrentThread.ManagedThreadId); } static void Main(string[] args) { #region //主線程id Console.WriteLine("主線程id: " + Thread.CurrentThread.ManagedThreadId); #endregion #region //傳遞一個(gè)方法給線程,執(zhí)行方法 Thread t = new Thread(Test); t.Start(); #endregion }
線程數(shù)據(jù)傳輸
傳遞單個(gè)參數(shù)
Thread
提供了一個(gè) Start(object parameter)
方法,可以在啟動(dòng)線程時(shí)傳遞一個(gè)參數(shù)。
static int Downlod(object obj) { string str = obj as string; Console.WriteLine(str); } static void Main(string[] args) { Thread t1 = new Thread(Downlod); //這里傳遞的參數(shù)是字符串 t1.Start("數(shù)據(jù)傳遞方法執(zhí)行 ,數(shù)據(jù)傳遞方法id: "+Thread.CurrentThread.ManagedThreadId); }
這種方式適用于需要傳遞單個(gè)參數(shù)的情況。
使用類的實(shí)例方法傳遞多個(gè)參數(shù)
先new一個(gè)類的實(shí)例,給實(shí)例字段賦值,調(diào)用類的實(shí)例,通過(guò)實(shí)例調(diào)用方法,構(gòu)造函數(shù)接收參數(shù),然后在線程中調(diào)用該實(shí)例的方法。
static void Main(string[] args) { // 使用 DownloadTool 實(shí)例化并傳遞數(shù)據(jù) DownloadTool downloadTool = new DownloadTool("www.baidu.com", "這是下載鏈接哦"); Thread thread = new Thread(downloadTool.Download); thread.Start(); } public class DownloadTool { private string url; private string message; public DownloadTool(string url, string message) { this.url = url; this.message = message; } public void Download() { Console.WriteLine("下載鏈接: " + url); Console.WriteLine("提示信息: " + message); Console.WriteLine("Download 線程 ID: " + Thread.CurrentThread.ManagedThreadId); } }
當(dāng) thread
線程啟動(dòng)時(shí),它會(huì)執(zhí)行 downloadTool.Download()
方法,輸出傳遞的數(shù)據(jù)。
線程優(yōu)先級(jí)
在 C# 中,可以使用 Thread.Priority
屬性來(lái)設(shè)置線程的優(yōu)先級(jí)。線程優(yōu)先級(jí)決定了操作系統(tǒng)在多線程環(huán)境中調(diào)度線程的順序,但并不保證高優(yōu)先級(jí)的線程總是比低優(yōu)先級(jí)的線程更早或更頻繁地執(zhí)行。
線程優(yōu)先級(jí)級(jí)別
C# 提供了五個(gè)線程優(yōu)先級(jí)級(jí)別,定義在 ThreadPriority
枚舉中:
- Lowest:最低優(yōu)先級(jí)。操作系統(tǒng)盡可能少地調(diào)度這個(gè)優(yōu)先級(jí)的線程。
- BelowNormal:低于正常的優(yōu)先級(jí)。優(yōu)先級(jí)比 Normal 低,但高于 Lowest。
- Normal:默認(rèn)優(yōu)先級(jí),大多數(shù)線程默認(rèn)的優(yōu)先級(jí)。適用于一般用途。
- AboveNormal:高于正常的優(yōu)先級(jí)。操作系統(tǒng)更傾向于調(diào)度這個(gè)優(yōu)先級(jí)的線程。
- Highest:最高優(yōu)先級(jí)。操作系統(tǒng)盡可能多地調(diào)度這個(gè)優(yōu)先級(jí)的線程。
internal class Program { static void A() { int i = 0; while (true) { i++; Console.WriteLine($"A 輸出第{i}"); Thread.Sleep(1000); } } static void B() { int i = 0; while (true) { i++; Console.WriteLine($"B 輸出第{i}"); Thread.Sleep(1000); } } static void Main(string[] args) { //在C#中,線程的優(yōu)先級(jí)可以通過(guò)Thread.Priority屬性來(lái)設(shè)置和獲取。 // Lowest: 線程的優(yōu)先級(jí)是最低的。在系統(tǒng)中存在其他活動(dòng)線程時(shí),此優(yōu)先級(jí)的線程很少得到執(zhí)行。 //BelowNormal: 線程的優(yōu)先級(jí)低于正常線程。 //Normal: 線程的優(yōu)先級(jí)是普通的,這是線程的默認(rèn)優(yōu)先級(jí)。 //AboveNormal: 線程的優(yōu)先級(jí)高于正常線程。 //Highest: 線程的優(yōu)先級(jí)是最高的。此優(yōu)先級(jí)的線程會(huì)盡量?jī)?yōu)先于其他所有優(yōu)先級(jí)的線程執(zhí)行。 Thread a = new Thread(A); Thread b = new Thread(B); a.Priority = ThreadPriority.Highest; a.Start(); b.Priority = ThreadPriority.Lowest; b.Start(); Console.WriteLine("按任意鍵停止線程..."); Console.ReadKey(); a.Join(); b.Join(); Console.WriteLine("線程已停止"); } }
A
被設(shè)置為最高優(yōu)先級(jí)ThreadPriority.Highest
。B
被設(shè)置為最低優(yōu)先級(jí)ThreadPriority.Lowest
。
注意事項(xiàng)
- 優(yōu)先級(jí)不是絕對(duì)控制:操作系統(tǒng)可能會(huì)忽略優(yōu)先級(jí)設(shè)置,特別是在資源有限的系統(tǒng)中。高優(yōu)先級(jí)線程不一定會(huì)一直執(zhí)行,也不能阻止低優(yōu)先級(jí)線程的執(zhí)行。
- 使用優(yōu)先級(jí)的適用場(chǎng)景:設(shè)置線程優(yōu)先級(jí)可能適用于實(shí)時(shí)系統(tǒng)(例如,某些任務(wù)需要優(yōu)先處理)。但是,大多數(shù)應(yīng)用程序通??梢允褂媚J(rèn)的
Normal
優(yōu)先級(jí)。 - 避免使用過(guò)多的高優(yōu)先級(jí)線程:如果所有線程都被設(shè)置為
Highest
,系統(tǒng)的整體性能可能會(huì)下降,甚至導(dǎo)致線程爭(zhēng)用 CPU 資源的情況。 - CPU 密集型任務(wù):在 CPU 密集型任務(wù)中,優(yōu)先級(jí)可能會(huì)對(duì)性能產(chǎn)生較大影響,因?yàn)閮?yōu)先級(jí)高的線程可能會(huì)占用更多的 CPU 時(shí)間。
線程優(yōu)先級(jí)的最佳實(shí)踐
- 默認(rèn)使用
Normal
優(yōu)先級(jí),除非有特殊原因。 - 避免濫用
Highest
優(yōu)先級(jí),因?yàn)樗鼤?huì)對(duì)系統(tǒng)資源產(chǎn)生影響。 - 在 I/O 密集型的線程中,優(yōu)先級(jí)通常不會(huì)有顯著差異,因?yàn)檫@些線程在等待 I/O 操作完成時(shí),CPU 會(huì)調(diào)度其他線程。
通過(guò)合理設(shè)置線程優(yōu)先級(jí),可以幫助操作系統(tǒng)更好地調(diào)度線程,以滿足應(yīng)用程序的需求。 但通常在 .NET 應(yīng)用程序中,多數(shù)情況下使用默認(rèn)的 Normal
優(yōu)先級(jí)就足夠了。
線程池
線程池 (ThreadPool
) 是一種高效的管理和調(diào)度線程的方式。線程池自動(dòng)管理線程的創(chuàng)建、重用和銷毀,從而減少了手動(dòng)創(chuàng)建和管理線程的開銷。
為什么使用線程池
- 性能更高:線程池會(huì)重用現(xiàn)有的線程,減少了創(chuàng)建和銷毀線程的開銷。
- 自動(dòng)管理:線程池會(huì)根據(jù)系統(tǒng)負(fù)載動(dòng)態(tài)調(diào)整線程數(shù)量。
- 避免線程資源不足:線程池限制了同時(shí)運(yùn)行的線程數(shù),避免了線程過(guò)多導(dǎo)致的資源耗盡問(wèn)題。
基本使用 ThreadPool.QueueUserWorkItem
方法將任務(wù)排入線程池隊(duì)列。
using System; using System.Threading; class Program { static void Main(string[] args) { // 將任務(wù)排入線程池 ThreadPool.QueueUserWorkItem(DoWork, "任務(wù) 1"); ThreadPool.QueueUserWorkItem(DoWork, "任務(wù) 2"); Console.WriteLine("主線程完成"); Thread.Sleep(3000); // 等待線程池中的任務(wù)完成 } static void DoWork(object state) { string taskName = (string)state; Console.WriteLine($"{taskName} 開始執(zhí)行 - 線程ID: {Thread.CurrentThread.ManagedThreadId}"); Thread.Sleep(1000); // 模擬耗時(shí)操作 Console.WriteLine($"{taskName} 執(zhí)行完成 - 線程ID: {Thread.CurrentThread.ManagedThreadId}"); } }
運(yùn)行結(jié)果QueueUserWorkItem自動(dòng)分配線程來(lái)執(zhí)行任務(wù)。
QueueUserWorkItem
方法將任務(wù)排入線程池。它接收一個(gè)委托(即方法)和一個(gè)可選的狀態(tài)對(duì)象(傳遞給方法的數(shù)據(jù))。
DoWork
方法接受一個(gè)參數(shù) state
,這是從 QueueUserWorkItem
傳遞的。
Thread.Sleep(3000)
確保主線程不會(huì)立即退出,使得線程池中的任務(wù)有機(jī)會(huì)完成。
使用帶返回值的線程池任務(wù)
C# 中的 ThreadPool
通常不直接支持返回值。如果需要獲得任務(wù)結(jié)果,可以使用 Task
,因?yàn)?Task
本質(zhì)上也是線程池的一部分。Task
更適合于帶返回值的異步操作。這里使用 Task.Run
來(lái)代替 ThreadPool
:
static void Main(string[] args) { //使用tesk多線程 int a= Task.Run(() => { int a = Dowload(); return a; }).Result; Task<int> task = Task<int>.Run(()=>{ int a = Dowload(); return a; }); //初始化一個(gè)CancellationTokenSource實(shí)例 CancellationTokenSource source = new CancellationTokenSource(); //task.Start(); task.Wait(1000); source.Cancel(); int result = task.Result; Console.WriteLine(result); Console.WriteLine($"tesk返回值{a}"); } static int Dowload() { int a = 0; for (int i = 0; i < 10; i++) { a= a + i + 1; } int? id= Task.CurrentId; Console.WriteLine("Current thread ID: " + id); return a; }
線程池的限制
- 任務(wù)運(yùn)行時(shí)間過(guò)長(zhǎng):線程池中的線程本質(zhì)上是共享資源,如果某個(gè)任務(wù)運(yùn)行時(shí)間太長(zhǎng),將會(huì)占用線程池中的線程,導(dǎo)致其他任務(wù)無(wú)法及時(shí)執(zhí)行。
- 不適合實(shí)時(shí)系統(tǒng):線程池中的任務(wù)調(diào)度是由系統(tǒng)管理的,無(wú)法保證精確的實(shí)時(shí)性。
- 有限的線程數(shù)量:在高并發(fā)場(chǎng)景中,如果線程池中的線程全部被占用,新的任務(wù)將會(huì)等待,直到有線程可用。
線程池總結(jié)
線程池是一種高效的并發(fā)處理方式,適合于大多數(shù)輕量級(jí)的后臺(tái)任務(wù)。在現(xiàn)代 C# 編程中,建議使用 Task
和 async/await
進(jìn)行異步操作,因?yàn)樗鼈兡芎?jiǎn)化代碼,并且使用底層的線程池來(lái)管理線程。如果需要精確控制線程的執(zhí)行,通常建議使用手動(dòng)管理的 Thread
等。
線程鎖
使用多線程,在多線程編程中,如果多個(gè)線程同時(shí)訪問(wèn)和修改共享資源(如全局變量、文件、數(shù)據(jù)庫(kù)等),可能會(huì)導(dǎo)致數(shù)據(jù)不一致或競(jìng)爭(zhēng)條件。為了避免這種情況,多線程鎖通過(guò)控制對(duì)共享資源的訪問(wèn)來(lái)保證線程安全性。
資源沖突示例:
static void Main(string[] args) { //調(diào)用方法循環(huán)創(chuàng)建新的線程來(lái)執(zhí)行方法 StateObject state = new StateObject(); for (int i = 0; i < 30; i++) { Thread thread = new Thread(state.ChangState); thread.Start(); } Console.ReadKey(); } //一個(gè)StateObject類 public class StateObject { private int state = 5; //里面有個(gè)狀態(tài)改變方法,當(dāng)狀態(tài)等于5的時(shí)候進(jìn)入到方法中,然后state+1 打印的應(yīng)該是6 和線程id public void ChangState() { if (state == 5) { state++; Console.WriteLine($"state:{state} 線程id:" + Thread.CurrentThread.ManagedThreadId); } state = 5; } }
運(yùn)行結(jié)果:
因?yàn)橘Y源沖突的原因,有一些線程執(zhí)行的時(shí)候,可能另外一個(gè)線程沒(méi)有執(zhí)行完,另外一個(gè)線程就已經(jīng)進(jìn)入到方法里面了。因?yàn)橛行薷牡牟僮鲗?dǎo)致State狀態(tài)混亂,資源沖突。這時(shí)候我們就需要一個(gè)東西來(lái)維護(hù),讓線程執(zhí)行指定方法時(shí)一個(gè)一個(gè)的執(zhí)行,不會(huì)沖突。
簡(jiǎn)單使用lock鎖
1.創(chuàng)建一個(gè)private readonly object lockObject = new object(); // 鎖對(duì)象
2.使用lock (lockObject){
//業(yè)務(wù)代碼,執(zhí)行到有修改的操作的代碼
} // 使用鎖對(duì)象來(lái)確保線程安全
static void Main(string[] args) { StateObject state = new StateObject(); for (int i = 0; i < 100; i++) { Thread thread = new Thread(state.ChangState); thread.Start(); } Console.ReadKey(); } public class StateObject { private object _lock = new object(); private int state = 5; public void ChangState() { //使用鎖保證每次執(zhí)行方法的都是一個(gè)線程,防止資源沖突 lock (_lock) { if (state == 5) { state++; Console.WriteLine($"state:{state} 線程id:" + Thread.CurrentThread.ManagedThreadId); } state = 5; } } }
這樣運(yùn)行起來(lái)就能把每個(gè)線程操作隔離開,保證state的狀態(tài)不會(huì)沖突。
為什么要?jiǎng)?chuàng)建一個(gè) object
對(duì)象作為鎖?
- 專用性:創(chuàng)建一個(gè)專用的鎖對(duì)象(如
private readonly object lockObject = new object();
),可以確保鎖定的是特定的同步邏輯,而不是其他對(duì)象。這有助于避免意外的鎖沖突或死鎖。 - 避免使用其他共享對(duì)象:雖然可以使用任意的引用類型對(duì)象作為鎖對(duì)象(包括
this
或Type
對(duì)象),但這可能會(huì)帶來(lái)不必要的風(fēng)險(xiǎn),尤其是在public
方法或?qū)ο笾校@樣可能會(huì)導(dǎo)致意外的鎖定沖突。
常見問(wèn)題死鎖:
鎖并不是萬(wàn)能,也不是有鎖就是最好,要看情況使用,鎖也會(huì)產(chǎn)生問(wèn)題,常見的問(wèn)題就是死鎖等問(wèn)題。
演示死鎖:
thread1方法1 的鎖里面嵌套鎖住了thread2的鎖,thread2方法2的鎖里面嵌套鎖住了thread1的鎖,這種鎖與鎖嵌套使用,就是容易出問(wèn)題。導(dǎo)致線程鎖死程序無(wú)法動(dòng)彈。
static void Main(string[] args) { DeadlockExample deadlockExample = new DeadlockExample(); Thread t1 = new Thread(deadlockExample.Thread1); Thread t2 = new Thread(deadlockExample.Thread2); t1.Start(); t2.Start(); t1.Join(); t2.Join(); Console.ReadKey(); } } public class DeadlockExample { private static object lock1 = new object(); private static object lock2 = new object(); public void Thread1() { lock (lock1) { Console.WriteLine("線程1:已獲取鎖1,正在等待鎖2。。。"); Thread.Sleep(100); // 模擬某些工作 lock (lock2) { Console.WriteLine("線程1:獲得鎖2"); } } } public void Thread2() { lock (lock2) { Console.WriteLine("線程2:已獲取鎖2,正在等待鎖1。。。"); Thread.Sleep(100); // 模擬某些工作 lock (lock1) { Console.WriteLine("線程2:獲得鎖1"); } } } }
運(yùn)行結(jié)果:
死鎖發(fā)生在兩個(gè)或多個(gè)線程相互等待對(duì)方持有的資源,導(dǎo)致所有線程都無(wú)法繼續(xù)執(zhí)行。
總結(jié):
多線程鎖在 C# 中主要用于解決以下問(wèn)題:
- 競(jìng)態(tài)條件:通過(guò)鎖機(jī)制防止多個(gè)線程同時(shí)訪問(wèn)和修改共享資源,確保數(shù)據(jù)一致性。
- 死鎖:防止多個(gè)線程相互等待資源,通過(guò)鎖的順序或者避免嵌套鎖來(lái)解決。
- 資源饑餓:確保每個(gè)線程都能獲取資源,使用
Monitor.TryEnter
等機(jī)制防止無(wú)限等待。 - 讀寫鎖:允許多個(gè)線程并發(fā)讀取資源,但寫入時(shí)互斥,適合讀多寫少的場(chǎng)景。
C# 提供了多種鎖機(jī)制,開發(fā)者可以根據(jù)應(yīng)用場(chǎng)景選擇合適的鎖類型。
如果不想使用 lock
關(guān)鍵字,C# 還提供了其他鎖機(jī)制,比如 Mutex
、Semaphore
、Monitor
等
到此這篇關(guān)于C#多線程基本使用和探討的文章就介紹到這了,更多相關(guān)C#多線程使用內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- C#使用讀寫鎖解決多線程并發(fā)問(wèn)題
- C# 使用CancellationTokenSource取消多線程
- C# 并行和多線程編程——認(rèn)識(shí)和使用Task
- C#使用讀寫鎖三行代碼簡(jiǎn)單解決多線程并發(fā)的問(wèn)題
- 解析C#多線程編程中異步多線程的實(shí)現(xiàn)及線程池的使用
- C#使用Parallel類進(jìn)行多線程編程實(shí)例
- C#中WPF使用多線程調(diào)用窗體組件的方法
- C#多線程學(xué)習(xí)之(五)使用定時(shí)器進(jìn)行多線程的自動(dòng)管理
- C#多線程學(xué)習(xí)之(四)使用線程池進(jìn)行多線程的自動(dòng)管理
相關(guān)文章
用序列化實(shí)現(xiàn)List<T> 實(shí)例的深復(fù)制(推薦)
下面小編就為大家?guī)?lái)一篇用序列化實(shí)現(xiàn)List<T> 實(shí)例的深復(fù)制(推薦)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-02-02c#中利用委托反射將DataTable轉(zhuǎn)換為實(shí)體集的代碼
c#中利用委托反射將DataTable轉(zhuǎn)換為實(shí)體集的代碼,需要的朋友可以參考下2012-10-10WPF實(shí)現(xiàn)列表分頁(yè)控件的示例代碼
這篇文章主要為大家詳細(xì)介紹了如何利用WPF實(shí)現(xiàn)列表分頁(yè)控件,文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)或工作有一定幫助,感興趣的小伙伴可以了解一下2022-10-10