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ú)立的線程來執(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í)例,通過實(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 屬性來設(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í)可以通過Thread.Priority屬性來設(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ì)盡量優(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í)的適用場景:設(shè)置線程優(yōu)先級(jí)可能適用于實(shí)時(shí)系統(tǒng)(例如,某些任務(wù)需要優(yōu)先處理)。但是,大多數(shù)應(yīng)用程序通??梢允褂媚J(rèn)的
Normal優(yōu)先級(jí)。 - 避免使用過多的高優(yōu)先級(jí)線程:如果所有線程都被設(shè)置為
Highest,系統(tǒng)的整體性能可能會(huì)下降,甚至導(dǎo)致線程爭用 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)度其他線程。
通過合理設(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ù),避免了線程過多導(dǎo)致的資源耗盡問題。
基本使用 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)分配線程來執(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 來代替 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í)間過長:線程池中的線程本質(zhì)上是共享資源,如果某個(gè)任務(wù)運(yùn)行時(shí)間太長,將會(huì)占用線程池中的線程,導(dǎo)致其他任務(wù)無法及時(shí)執(zhí)行。
- 不適合實(shí)時(shí)系統(tǒng):線程池中的任務(wù)調(diào)度是由系統(tǒng)管理的,無法保證精確的實(shí)時(shí)性。
- 有限的線程數(shù)量:在高并發(fā)場景中,如果線程池中的線程全部被占用,新的任務(wù)將會(huì)等待,直到有線程可用。
線程池總結(jié)
線程池是一種高效的并發(fā)處理方式,適合于大多數(shù)輕量級(jí)的后臺(tái)任務(wù)。在現(xiàn)代 C# 編程中,建議使用 Task 和 async/await 進(jìn)行異步操作,因?yàn)樗鼈兡芎喕a,并且使用底層的線程池來管理線程。如果需要精確控制線程的執(zhí)行,通常建議使用手動(dòng)管理的 Thread 等。
線程鎖
使用多線程,在多線程編程中,如果多個(gè)線程同時(shí)訪問和修改共享資源(如全局變量、文件、數(shù)據(jù)庫等),可能會(huì)導(dǎo)致數(shù)據(jù)不一致或競爭條件。為了避免這種情況,多線程鎖通過控制對(duì)共享資源的訪問來保證線程安全性。
資源沖突示例:
static void Main(string[] args)
{
//調(diào)用方法循環(huán)創(chuàng)建新的線程來執(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è)線程沒有執(zhí)行完,另外一個(gè)線程就已經(jīng)進(jìn)入到方法里面了。因?yàn)橛行薷牡牟僮鲗?dǎo)致State狀態(tài)混亂,資源沖突。這時(shí)候我們就需要一個(gè)東西來維護(hù),讓線程執(zhí)行指定方法時(shí)一個(gè)一個(gè)的執(zhí)行,不會(huì)沖突。

簡單使用lock鎖
1.創(chuàng)建一個(gè)private readonly object lockObject = new object(); // 鎖對(duì)象
2.使用lock (lockObject){
//業(yè)務(wù)代碼,執(zhí)行到有修改的操作的代碼
} // 使用鎖對(duì)象來確保線程安全
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)行起來就能把每個(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ì)帶來不必要的風(fēng)險(xiǎn),尤其是在public方法或?qū)ο笾校@樣可能會(huì)導(dǎo)致意外的鎖定沖突。
常見問題死鎖:
鎖并不是萬能,也不是有鎖就是最好,要看情況使用,鎖也會(huì)產(chǎn)生問題,常見的問題就是死鎖等問題。
演示死鎖:
thread1方法1 的鎖里面嵌套鎖住了thread2的鎖,thread2方法2的鎖里面嵌套鎖住了thread1的鎖,這種鎖與鎖嵌套使用,就是容易出問題。導(dǎo)致線程鎖死程序無法動(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)致所有線程都無法繼續(xù)執(zhí)行。

總結(jié):
多線程鎖在 C# 中主要用于解決以下問題:
- 競態(tài)條件:通過鎖機(jī)制防止多個(gè)線程同時(shí)訪問和修改共享資源,確保數(shù)據(jù)一致性。
- 死鎖:防止多個(gè)線程相互等待資源,通過鎖的順序或者避免嵌套鎖來解決。
- 資源饑餓:確保每個(gè)線程都能獲取資源,使用
Monitor.TryEnter等機(jī)制防止無限等待。 - 讀寫鎖:允許多個(gè)線程并發(fā)讀取資源,但寫入時(shí)互斥,適合讀多寫少的場景。
C# 提供了多種鎖機(jī)制,開發(fā)者可以根據(jù)應(yīng)用場景選擇合適的鎖類型。
如果不想使用 lock 關(guān)鍵字,C# 還提供了其他鎖機(jī)制,比如 Mutex、Semaphore、Monitor 等
到此這篇關(guān)于C#多線程基本使用和探討的文章就介紹到這了,更多相關(guān)C#多線程使用內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- C#使用讀寫鎖解決多線程并發(fā)問題
- C# 使用CancellationTokenSource取消多線程
- C# 并行和多線程編程——認(rèn)識(shí)和使用Task
- C#使用讀寫鎖三行代碼簡單解決多線程并發(fā)的問題
- 解析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ī)硪黄眯蛄谢瘜?shí)現(xiàn)List<T> 實(shí)例的深復(fù)制(推薦)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-02-02
c#中利用委托反射將DataTable轉(zhuǎn)換為實(shí)體集的代碼
c#中利用委托反射將DataTable轉(zhuǎn)換為實(shí)體集的代碼,需要的朋友可以參考下2012-10-10

