.NET中多線程任務(wù)實(shí)現(xiàn)的幾種方法小結(jié)
1. 引言
在現(xiàn)代軟件開發(fā)中,多線程編程已成為提高應(yīng)用程序性能和響應(yīng)能力的關(guān)鍵技術(shù)。.NET框架提供了豐富的多線程編程模型和API,使開發(fā)人員能夠根據(jù)不同的場(chǎng)景需求選擇最合適的實(shí)現(xiàn)方式。本文將全面分析.NET平臺(tái)下多線程任務(wù)實(shí)現(xiàn)的幾種主要方法,并深入探討線程等待機(jī)制,幫助開發(fā)人員構(gòu)建高效、可靠的并發(fā)應(yīng)用程序。
多線程編程雖然強(qiáng)大,但也帶來了復(fù)雜性,如競(jìng)態(tài)條件、死鎖、線程安全等問題。理解.NET提供的各種多線程實(shí)現(xiàn)方式及其適用場(chǎng)景,掌握線程同步與等待的正確方法,對(duì)于編寫健壯的并發(fā)代碼至關(guān)重要。本文將從基礎(chǔ)概念出發(fā),逐步深入,涵蓋從傳統(tǒng)的Thread類到現(xiàn)代的async/await模式等各種技術(shù),并提供實(shí)際代碼示例和最佳實(shí)踐建議。
2.NET多線程編程基礎(chǔ)
2.1 線程概念回顧
線程是操作系統(tǒng)能夠進(jìn)行運(yùn)算調(diào)度的最小單位,它被包含在進(jìn)程之中,是進(jìn)程中的實(shí)際運(yùn)作單位。一個(gè)進(jìn)程可以包含多個(gè)線程,這些線程共享進(jìn)程的資源,但各自擁有獨(dú)立的執(zhí)行路徑和調(diào)用棧。
在.NET中,線程分為兩種主要類型:
- 前臺(tái)線程:這類線程會(huì)阻止進(jìn)程終止,直到所有前臺(tái)線程都完成執(zhí)行。
- 后臺(tái)線程:這類線程不會(huì)阻止進(jìn)程終止,當(dāng)所有前臺(tái)線程結(jié)束時(shí),所有后臺(tái)線程會(huì)被自動(dòng)終止。
多線程編程的主要優(yōu)勢(shì)包括:
- 提高CPU利用率
- 改善應(yīng)用程序響應(yīng)性
- 簡(jiǎn)化異步操作模型
- 充分利用多核處理器
然而,多線程編程也帶來了一些挑戰(zhàn):
- 線程安全問題(競(jìng)態(tài)條件)
- 死鎖和活鎖風(fēng)險(xiǎn)
- 上下文切換開銷
- 調(diào)試復(fù)雜性增加
2.2 .NET線程模型概述
.NET框架提供了多層次的線程抽象,從低級(jí)的Thread類到高級(jí)的Task Parallel Library (TPL)和async/await模式,開發(fā)者可以根據(jù)需求選擇不同層次的抽象。
.NET線程模型的關(guān)鍵組件:
- Thread類:最基本的線程創(chuàng)建和控制方式,提供了對(duì)線程的直接控制。
- ThreadPool:一個(gè)共享的線程池,用于執(zhí)行短期的后臺(tái)任務(wù),減少線程創(chuàng)建和銷毀的開銷。
- Task Parallel Library (TPL):引入.NET Framework 4.0,提供了更高級(jí)的任務(wù)抽象,簡(jiǎn)化了并行編程。
- Parallel類:TPL的一部分,提供了簡(jiǎn)單的數(shù)據(jù)并行和任務(wù)并行方法。
- BackgroundWorker:主要用于Windows Forms應(yīng)用程序,簡(jiǎn)化了后臺(tái)操作與UI更新的交互。
- async/await:C# 5.0引入的異步編程模型,提供了更簡(jiǎn)潔的異步代碼編寫方式。
3. 多線程任務(wù)實(shí)現(xiàn)方法
3.1 Thread類實(shí)現(xiàn)
System.Threading.Thread類是.NET中最基礎(chǔ)的線程創(chuàng)建和控制方式。它提供了對(duì)線程生命周期的直接控制,包括創(chuàng)建、啟動(dòng)、暫停、恢復(fù)和終止線程。
創(chuàng)建和啟動(dòng)線程:
using System;
using System.Threading;
class Program
{
static void Main()
{
// 創(chuàng)建新線程
Thread thread = new Thread(new ThreadStart(WorkerMethod));
// 設(shè)置為后臺(tái)線程(可選)
thread.IsBackground = true;
// 啟動(dòng)線程
thread.Start();
// 主線程繼續(xù)執(zhí)行其他工作
for (int i = 0; i < 5; i++)
{
Console.WriteLine($"主線程: {i}");
Thread.Sleep(100);
}
// 等待工作線程完成(可選)
thread.Join();
Console.WriteLine("工作線程完成");
}
static void WorkerMethod()
{
for (int i = 0; i < 10; i++)
{
Console.WriteLine($"工作線程: {i}");
Thread.Sleep(200);
}
}
}
Thread類的關(guān)鍵特性:
1.線程控制:
- Start():開始線程執(zhí)行
- Join():等待線程完成
- Abort():強(qiáng)制終止線程(已過時(shí),不推薦使用)
- Suspend()和Resume():已過時(shí),不應(yīng)使用
2.線程屬性:
- IsBackground:獲取或設(shè)置是否為后臺(tái)線程
- Priority:設(shè)置線程優(yōu)先級(jí)(Normal, AboveNormal, BelowNormal, Highest, Lowest)
- ThreadState:獲取線程當(dāng)前狀態(tài)
- Name:設(shè)置線程名稱(調(diào)試有用)
3.線程數(shù)據(jù):
- ThreadStatic特性:標(biāo)記靜態(tài)字段為線程局部存儲(chǔ)
- ThreadLocal<T>:提供線程特定的數(shù)據(jù)存儲(chǔ)
4.優(yōu)點(diǎn):
- 提供對(duì)線程的精細(xì)控制
- 適用于需要長(zhǎng)期運(yùn)行或需要特定優(yōu)先級(jí)的線程
- 可以直接訪問底層線程API
5.缺點(diǎn):
- 創(chuàng)建和銷毀線程開銷較大
- 需要手動(dòng)管理線程生命周期
- 缺乏高級(jí)功能如任務(wù)延續(xù)、異常傳播等
3.2 ThreadPool實(shí)現(xiàn)
System.Threading.ThreadPool類提供了一個(gè)共享的線程池,用于執(zhí)行短期的后臺(tái)任務(wù)。線程池管理一組工作線程,根據(jù)需要?jiǎng)?chuàng)建新線程或重用現(xiàn)有線程,減少了線程創(chuàng)建和銷毀的開銷。
使用ThreadPool執(zhí)行任務(wù):
using System;
using System.Threading;
class Program
{
static void Main()
{
// 將工作項(xiàng)排隊(duì)到線程池
ThreadPool.QueueUserWorkItem(WorkerMethod, "參數(shù)1");
ThreadPool.QueueUserWorkItem(WorkerMethod, "參數(shù)2");
// 主線程繼續(xù)執(zhí)行其他工作
for (int i = 0; i < 5; i++)
{
Console.WriteLine($"主線程: {i}");
Thread.Sleep(100);
}
// 注意:ThreadPool沒有直接的等待機(jī)制
Console.ReadLine(); // 防止程序退出
}
static void WorkerMethod(object state)
{
string param = (string)state;
for (int i = 0; i < 3; i++)
{
Console.WriteLine($"{param}: {i}");
Thread.Sleep(200);
}
}
}ThreadPool的關(guān)鍵特性:
1.自動(dòng)管理:
- 根據(jù)需要?jiǎng)?chuàng)建和銷毀線程
- 重用現(xiàn)有空閑線程
- 限制最大線程數(shù)以防止資源耗盡
2.配置選項(xiàng):
- SetMinThreads()和SetMaxThreads():設(shè)置線程池的最小和最大線程數(shù)
- GetAvailableThreads():獲取可用線程數(shù)
3.適用場(chǎng)景:
- 短期運(yùn)行的任務(wù)
- 不需要精細(xì)控制的并行工作
- 不需要特定線程屬性的任務(wù)
4.優(yōu)點(diǎn):
- 減少線程創(chuàng)建和銷毀的開銷
- 自動(dòng)線程管理
- 適合大量短生命周期的任務(wù)
5.缺點(diǎn):
- 對(duì)線程控制有限(無法設(shè)置優(yōu)先級(jí)、名稱等)
- 不適合長(zhǎng)時(shí)間運(yùn)行的任務(wù)(可能阻塞線程池)
- 缺乏任務(wù)調(diào)度和協(xié)調(diào)功能
3.3 Task Parallel Library (TPL)
Task Parallel Library (TPL)是.NET Framework 4.0引入的一組API,簡(jiǎn)化了并行和異步編程。TPL的核心是System.Threading.Tasks.Task類,它代表一個(gè)異步操作。
使用Task執(zhí)行工作:
using System;
using System.Threading.Tasks;
???????class Program
{
static void Main()
{
// 創(chuàng)建并啟動(dòng)任務(wù)
Task task1 = Task.Run(() => WorkerMethod("任務(wù)1"));
Task task2 = Task.Run(() => WorkerMethod("任務(wù)2"));
// 主線程繼續(xù)執(zhí)行其他工作
for (int i = 0; i < 5; i++)
{
Console.WriteLine($"主線程: {i}");
Task.Delay(100).Wait();
}
// 等待所有任務(wù)完成
Task.WaitAll(task1, task2);
Console.WriteLine("所有任務(wù)完成");
}
static void WorkerMethod(string taskName)
{
for (int i = 0; i < 3; i++)
{
Console.WriteLine($"{taskName}: {i}");
Task.Delay(200).Wait();
}
}
}TPL的關(guān)鍵特性:
1.任務(wù)創(chuàng)建:
- Task.Run():最簡(jiǎn)單的方式創(chuàng)建和啟動(dòng)任務(wù)
- new Task() + Start():更精細(xì)的控制
- Task.Factory.StartNew():提供更多選項(xiàng)
2.任務(wù)控制:
- Wait():等待單個(gè)任務(wù)完成
- WaitAll():等待多個(gè)任務(wù)完成
- WaitAny():等待任意一個(gè)任務(wù)完成
- ContinueWith():任務(wù)完成后執(zhí)行延續(xù)操作
3.任務(wù)返回結(jié)果:
- Task<TResult>:可以返回結(jié)果的任務(wù)
- Result屬性:獲取任務(wù)結(jié)果(會(huì)阻塞直到任務(wù)完成)
4.異常處理:
- 任務(wù)異常會(huì)被捕獲并存儲(chǔ)在Exception屬性中
- 調(diào)用Wait()或訪問Result時(shí)會(huì)重新拋出異常
- 使用AggregateException處理多個(gè)異常
5.優(yōu)點(diǎn):
- 比Thread和ThreadPool更高級(jí)的抽象
- 支持任務(wù)組合和延續(xù)
- 更好的異常處理機(jī)制
- 與async/await完美集成
- 內(nèi)置取消支持(通過CancellationToken)
6.缺點(diǎn):
- 比直接使用Thread有輕微開銷
- 某些高級(jí)場(chǎng)景可能需要更底層的控制
3.4 Parallel類
System.Threading.Tasks.Parallel類是TPL的一部分,提供了簡(jiǎn)單的數(shù)據(jù)并行和任務(wù)并行方法。它特別適合對(duì)集合進(jìn)行并行操作。
使用Parallel類:
using System;
using System.Threading.Tasks;
???????class Program
{
static void Main()
{
// Parallel.For - 數(shù)據(jù)并行
Parallel.For(0, 10, i =>
{
Console.WriteLine($"For迭代 {i}, 線程ID: {Thread.CurrentThread.ManagedThreadId}");
Thread.Sleep(100);
});
// Parallel.ForEach - 數(shù)據(jù)并行
var data = new[] { "A", "B", "C", "D", "E" };
Parallel.ForEach(data, item =>
{
Console.WriteLine($"處理 {item}, 線程ID: {Thread.CurrentThread.ManagedThreadId}");
Thread.Sleep(200);
});
// Parallel.Invoke - 任務(wù)并行
Parallel.Invoke(
() => WorkerMethod("任務(wù)1"),
() => WorkerMethod("任務(wù)2"),
() => WorkerMethod("任務(wù)3")
);
}
static void WorkerMethod(string taskName)
{
for (int i = 0; i < 3; i++)
{
Console.WriteLine($"{taskName}: {i}");
Thread.Sleep(200);
}
}
}Parallel類的關(guān)鍵特性:
1.自動(dòng)并行化:
- 自動(dòng)決定并行度
- 自動(dòng)分區(qū)工作
- 自動(dòng)處理線程創(chuàng)建和管理
2.并行方法:
- Parallel.For:并行執(zhí)行for循環(huán)
- Parallel.ForEach:并行處理集合
- Parallel.Invoke:并行執(zhí)行多個(gè)委托
3.配置選項(xiàng):
- ParallelOptions:可以設(shè)置最大并行度、取消標(biāo)記等
- 支持負(fù)載均衡和工作竊取
4.優(yōu)點(diǎn):
- 簡(jiǎn)化數(shù)據(jù)并行編程
- 自動(dòng)優(yōu)化并行度
- 內(nèi)置負(fù)載均衡
- 與PLINQ(Parallel LINQ)集成良好
5.缺點(diǎn):
- 對(duì)并行循環(huán)的控制有限
- 不適合需要精細(xì)控制的任務(wù)
- 可能不適用于所有并行場(chǎng)景
3.5 BackgroundWorker組件
System.ComponentModel.BackgroundWorker是一個(gè)基于事件的組件,主要用于在Windows Forms和WPF應(yīng)用程序中簡(jiǎn)化后臺(tái)操作與UI更新的交互。
使用BackgroundWorker:
using System;
using System.ComponentModel;
using System.Threading;
using System.Windows.Forms;
???????class Program
{
static BackgroundWorker worker;
static void Main()
{
worker = new BackgroundWorker();
// 設(shè)置支持取消和進(jìn)度報(bào)告
worker.WorkerReportsProgress = true;
worker.WorkerSupportsCancellation = true;
// 事件處理
worker.DoWork += Worker_DoWork;
worker.ProgressChanged += Worker_ProgressChanged;
worker.RunWorkerCompleted += Worker_RunWorkerCompleted;
// 模擬UI線程
Console.WriteLine("按S開始工作,C取消工作");
while (true)
{
var key = Console.ReadKey(true);
if (key.Key == ConsoleKey.S && !worker.IsBusy)
{
worker.RunWorkerAsync("參數(shù)");
Console.WriteLine("工作已開始");
}
else if (key.Key == ConsoleKey.C && worker.IsBusy)
{
worker.CancelAsync();
Console.WriteLine("取消請(qǐng)求已發(fā)送");
}
}
}
static void Worker_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = (BackgroundWorker)sender;
string param = (string)e.Argument;
for (int i = 0; i <= 100; i++)
{
if (worker.CancellationPending)
{
e.Cancel = true;
return;
}
// 模擬工作
Thread.Sleep(50);
// 報(bào)告進(jìn)度
worker.ReportProgress(i, $"處理 {param} - {i}%");
}
e.Result = $"完成 {param}";
}
static void Worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
Console.WriteLine($"進(jìn)度: {e.ProgressPercentage}%, 狀態(tài): {e.UserState}");
}
static void Worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Cancelled)
{
Console.WriteLine("工作被取消");
}
else if (e.Error != null)
{
Console.WriteLine($"錯(cuò)誤: {e.Error.Message}");
}
else
{
Console.WriteLine($"結(jié)果: {e.Result}");
}
}
}BackgroundWorker的關(guān)鍵特性:
1.事件驅(qū)動(dòng)模型:
- DoWork:在后臺(tái)線程執(zhí)行
- ProgressChanged:在主線程報(bào)告進(jìn)度
- RunWorkerCompleted:在主線程通知完成
2.UI集成:
- 自動(dòng)將進(jìn)度和完成事件封送到UI線程
- 簡(jiǎn)化了UI更新
3.功能支持:
- 進(jìn)度報(bào)告
- 取消支持
- 錯(cuò)誤處理
4.優(yōu)點(diǎn):
- 簡(jiǎn)化UI應(yīng)用程序的后臺(tái)操作
- 自動(dòng)處理線程間通信
- 內(nèi)置進(jìn)度報(bào)告和取消支持
- 事件模型易于理解和使用
5.缺點(diǎn):
- 主要用于UI場(chǎng)景
- 不如Task靈活和強(qiáng)大
- 在現(xiàn)代應(yīng)用中逐漸被async/await取代
3.6 Async/Await模式
C# 5.0引入的async/await模式是.NET異步編程的重大改進(jìn),它允許以近乎同步的方式編寫異步代碼,大大簡(jiǎn)化了異步編程的復(fù)雜性。
使用async/await:
using System;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
Console.WriteLine("主線程開始");
// 啟動(dòng)異步任務(wù)
Task<string> task1 = DoWorkAsync("任務(wù)1");
Task<string> task2 = DoWorkAsync("任務(wù)2");
// 主線程可以繼續(xù)做其他工作
for (int i = 0; i < 3; i++)
{
Console.WriteLine($"主線程工作 {i}");
await Task.Delay(100);
}
// 等待任務(wù)完成并獲取結(jié)果
string result1 = await task1;
string result2 = await task2;
Console.WriteLine($"結(jié)果1: {result1}");
Console.WriteLine($"結(jié)果2: {result2}");
Console.WriteLine("所有工作完成");
}
static async Task<string> DoWorkAsync(string name)
{
Console.WriteLine($"{name} 開始");
for (int i = 0; i < 5; i++)
{
Console.WriteLine($"{name} 步驟 {i}");
await Task.Delay(200); // 模擬異步工作
}
return $"{name} 完成";
}
}async/await的關(guān)鍵特性:
1.語法簡(jiǎn)化:
- async修飾方法
- await等待異步操作完成
- 自動(dòng)生成狀態(tài)機(jī)處理異步流程
2.返回類型:
- Task:不返回值的異步方法
- Task<T>:返回值的異步方法
- ValueTask和ValueTask<T>:輕量級(jí)替代方案
3.異常處理:
- 使用try/catch處理異步異常
- 異常傳播與同步代碼一致
4.上下文捕獲:
- 默認(rèn)捕獲同步上下文(UI線程)
- 可使用ConfigureAwait(false)避免上下文捕獲
5.優(yōu)點(diǎn):
- 代碼結(jié)構(gòu)清晰,類似同步代碼
- 簡(jiǎn)化錯(cuò)誤處理
- 提高代碼可讀性和可維護(hù)性
- 與現(xiàn)有異步模式良好集成
6.缺點(diǎn):
- 需要理解底層機(jī)制以避免常見陷阱
- 過度使用可能導(dǎo)致性能問題
- 調(diào)試可能更復(fù)雜(調(diào)用棧、局部變量等)
3.7 各種方法的比較與選擇
| 方法 | 適用場(chǎng)景 | 優(yōu)點(diǎn) | 缺點(diǎn) | 推薦使用場(chǎng)景 |
|---|---|---|---|---|
| Thread | 需要精細(xì)控制線程的場(chǎng)景 | 完全控制線程生命周期 | 開銷大,管理復(fù)雜 | 長(zhǎng)期運(yùn)行的高優(yōu)先級(jí)任務(wù) |
| ThreadPool | 短期后臺(tái)任務(wù) | 自動(dòng)管理,開銷小 | 控制有限 | 大量短生命周期任務(wù) |
| TPL (Task) | 大多數(shù)并行/異步場(chǎng)景 | 高級(jí)抽象,功能豐富 | 輕微開銷 | 現(xiàn)代.NET應(yīng)用的主要選擇 |
| Parallel | 數(shù)據(jù)并行操作 | 簡(jiǎn)化并行循環(huán) | 靈活性有限 | 集合的并行處理 |
| BackgroundWorker | UI應(yīng)用的后臺(tái)任務(wù) | 簡(jiǎn)化UI更新 | 僅限UI場(chǎng)景 | 傳統(tǒng)WinForms/WPF應(yīng)用 |
| async/await | I/O密集型異步操作 | 代碼簡(jiǎn)潔,可讀性好 | 需要理解機(jī)制 | 現(xiàn)代異步編程首選 |
選擇指南:
- 對(duì)于現(xiàn)代.NET應(yīng)用:優(yōu)先考慮async/await和Task,它們提供了最佳的生產(chǎn)力和性能平衡。
- 對(duì)于CPU密集型并行計(jì)算:考慮Parallel類或PLINQ。
- 對(duì)于需要精細(xì)控制的線程:使用Thread類,但需謹(jǐn)慎管理資源。
- 對(duì)于UI應(yīng)用的后臺(tái)操作:現(xiàn)代應(yīng)用使用async/await,傳統(tǒng)應(yīng)用可使用BackgroundWorker。
- 對(duì)于大量短期任務(wù):使用ThreadPool或Task(內(nèi)部使用線程池)。
4. 線程等待機(jī)制詳解
在多線程編程中,線程等待和同步是核心概念。.NET提供了多種機(jī)制來實(shí)現(xiàn)線程間的協(xié)調(diào)和同步。
4.1 基本等待方法
Thread.Join()
Thread.Join()方法阻塞調(diào)用線程,直到指定的線程終止。
Thread workerThread = new Thread(WorkerMethod);
workerThread.Start();
// 主線程等待workerThread完成
workerThread.Join();
Console.WriteLine("工作線程已完成");
Task.Wait() / Task.WaitAll() / Task.WaitAny()
Task task1 = Task.Run(() => WorkerMethod("任務(wù)1"));
Task task2 = Task.Run(() => WorkerMethod("任務(wù)2"));
// 等待單個(gè)任務(wù)
task1.Wait();
// 等待所有任務(wù)
Task.WaitAll(task1, task2);
// 等待任意一個(gè)任務(wù)
Task.WaitAny(task1, task2);
async/await
async Task Main()
{
Task task1 = DoWorkAsync("任務(wù)1");
Task task2 = DoWorkAsync("任務(wù)2");
// 異步等待
await task1;
await task2;
// 或者同時(shí)等待多個(gè)任務(wù)
await Task.WhenAll(task1, task2);
// 或 await Task.WhenAny(task1, task2);
}
4.2 同步原語
.NET提供了多種同步原語來處理線程同步:
1.lock語句(Monitor):
- 最簡(jiǎn)單的同步機(jī)制
- 確保同一時(shí)間只有一個(gè)線程可以訪問代碼塊
private static readonly object _lock = new object();
void CriticalSection()
{
lock (_lock)
{
// 臨界區(qū)代碼
}
}
2.Mutex:
- 跨進(jìn)程的同步原語
- 比lock更重量級(jí)
using var mutex = new Mutex(false, "Global\\MyMutex");
try
{
mutex.WaitOne();
// 臨界區(qū)代碼
}
finally
{
mutex.ReleaseMutex();
}
3.Semaphore/SemaphoreSlim:
- 限制可以同時(shí)訪問資源的線程數(shù)
- SemaphoreSlim更輕量,適合單進(jìn)程
private static SemaphoreSlim _semaphore = new SemaphoreSlim(3); // 允許3個(gè)線程同時(shí)進(jìn)入
async Task AccessResource()
{
await _semaphore.WaitAsync();
try
{
// 受保護(hù)的代碼
}
finally
{
_semaphore.Release();
}
}
4.ManualResetEvent/AutoResetEvent:
- 線程間信號(hào)通知機(jī)制
- ManualResetEvent需要手動(dòng)重置
- AutoResetEvent自動(dòng)重置
private static ManualResetEvent _mre = new ManualResetEvent(false);
???????void Worker()
{
Console.WriteLine("工作線程等待信號(hào)...");
_mre.WaitOne();
Console.WriteLine("工作線程收到信號(hào)");
}
void SetSignal()
{
Thread.Sleep(2000);
_mre.Set(); // 發(fā)送信號(hào)
}
5.Barrier:
- 允許多個(gè)線程在某個(gè)點(diǎn)同步
- 所有線程到達(dá)屏障點(diǎn)后才能繼續(xù)
Barrier barrier = new Barrier(3); // 3個(gè)參與者
void Worker(object name)
{
Console.WriteLine($"{name} 到達(dá)階段1");
barrier.SignalAndWait();
Console.WriteLine($"{name} 到達(dá)階段2");
barrier.SignalAndWait();
}
// 啟動(dòng)3個(gè)線程
new Thread(Worker).Start("線程1");
new Thread(Worker).Start("線程2");
new Thread(Worker).Start("線程3");
6.CountdownEvent:
- 等待多個(gè)信號(hào)
- 計(jì)數(shù)器歸零時(shí)釋放等待線程
CountdownEvent cde = new CountdownEvent(3); // 初始計(jì)數(shù)3
void Worker(object name)
{
Thread.Sleep(1000);
Console.WriteLine($"{name} 完成工作");
cde.Signal(); // 計(jì)數(shù)減1
}
// 啟動(dòng)3個(gè)線程
new Thread(Worker).Start("線程1");
new Thread(Worker).Start("線程2");
new Thread(Worker).Start("線程3");
???????cde.Wait(); // 等待計(jì)數(shù)歸零
Console.WriteLine("所有工作完成");7.ReaderWriterLockSlim:
允許多個(gè)讀取器或單個(gè)寫入器
提高讀取密集型資源的性能
ReaderWriterLockSlim rwLock = new ReaderWriterLockSlim();
void ReadData()
{
rwLock.EnterReadLock();
try
{
// 讀取操作
}
finally
{
rwLock.ExitReadLock();
}
}
???????void WriteData()
{
rwLock.EnterWriteLock();
try
{
// 寫入操作
}
finally
{
rwLock.ExitWriteLock();
}
}4.3 異步等待
在現(xiàn)代.NET應(yīng)用中,異步等待(async/await)是處理并發(fā)和異步操作的首選方式。
關(guān)鍵異步等待方法:
1.Task.Delay:
異步版本的Thread.Sleep
不會(huì)阻塞線程
async Task ProcessAsync()
{
Console.WriteLine("開始處理");
await Task.Delay(1000); // 異步等待1秒
Console.WriteLine("處理完成");
}
2.Task.WhenAll:
等待多個(gè)任務(wù)全部完成
async Task ProcessMultipleAsync()
{
Task task1 = DoWorkAsync("任務(wù)1");
Task task2 = DoWorkAsync("任務(wù)2");
Task task3 = DoWorkAsync("任務(wù)3");
await Task.WhenAll(task1, task2, task3);
Console.WriteLine("所有任務(wù)完成");
}
3.Task.WhenAny:
等待任意一個(gè)任務(wù)完成
async Task ProcessFirstCompletedAsync()
{
Task<int> task1 = DoWorkWithResultAsync("任務(wù)1", 2000);
Task<int> task2 = DoWorkWithResultAsync("任務(wù)2", 1000);
Task<int> completedTask = await Task.WhenAny(task1, task2);
int result = await completedTask;
Console.WriteLine($"第一個(gè)完成的任務(wù)結(jié)果: {result}");
}
4.CancellationToken:
支持異步取消操作
async Task LongRunningOperationAsync(CancellationToken cancellationToken)
{
for (int i = 0; i < 10; i++)
{
cancellationToken.ThrowIfCancellationRequested();
Console.WriteLine($"工作進(jìn)度: {i * 10}%");
await Task.Delay(500, cancellationToken);
}
}
???????// 使用示例
var cts = new CancellationTokenSource();
try
{
// 3秒后取消
cts.CancelAfter(3000);
await LongRunningOperationAsync(cts.Token);
}
catch (OperationCanceledException)
{
Console.WriteLine("操作被取消");
}4.4 超時(shí)處理
在多線程編程中,處理超時(shí)是防止死鎖和響應(yīng)遲緩的重要手段。
Thread.Join超時(shí):
Thread workerThread = new Thread(WorkerMethod);
workerThread.Start();
if (!workerThread.Join(TimeSpan.FromSeconds(3)))
{
Console.WriteLine("工作線程未在3秒內(nèi)完成");
}
Task.Wait超時(shí):
Task longRunningTask = Task.Run(() => Thread.Sleep(5000));
try
{
if (!longRunningTask.Wait(TimeSpan.FromSeconds(3)))
{
Console.WriteLine("任務(wù)未在3秒內(nèi)完成");
}
}
catch (AggregateException ae)
{
// 處理異常
}
異步等待超時(shí):
async Task ProcessWithTimeoutAsync()
{
Task longTask = LongRunningOperationAsync();
Task timeoutTask = Task.Delay(3000);
Task completedTask = await Task.WhenAny(longTask, timeoutTask);
if (completedTask == timeoutTask)
{
Console.WriteLine("操作超時(shí)");
// 可能的取消邏輯
}
else
{
Console.WriteLine("操作按時(shí)完成");
}
}同步原語超時(shí):
// 使用Monitor.TryEnter
if (Monitor.TryEnter(_lock, TimeSpan.FromSeconds(1)))
{
try
{
// 臨界區(qū)代碼
}
finally
{
Monitor.Exit(_lock);
}
}
else
{
Console.WriteLine("獲取鎖超時(shí)");
}
???????// 使用SemaphoreSlim.WaitAsync
if (await _semaphore.WaitAsync(TimeSpan.FromSeconds(1)))
{
try
{
// 受保護(hù)的代碼
}
finally
{
_semaphore.Release();
}
}
else
{
Console.WriteLine("獲取信號(hào)量超時(shí)");
}5. 高級(jí)主題與最佳實(shí)踐
5.1 線程安全與同步
線程安全的基本原則:
- 避免共享狀態(tài):最好的線程同步是不需要同步。盡可能設(shè)計(jì)無狀態(tài)或線程隔離的組件。
- 不可變對(duì)象:使用不可變對(duì)象可以安全地在線程間共享。
- 最小化同步范圍:只同步必要的代碼塊,減少鎖競(jìng)爭(zhēng)。
- 選擇合適的同步原語:根據(jù)場(chǎng)景選擇最輕量級(jí)的同步機(jī)制。
常見線程安全問題:
1.競(jìng)態(tài)條件:當(dāng)多個(gè)線程訪問共享數(shù)據(jù)并嘗試同時(shí)修改它時(shí)發(fā)生。
解決方案:使用適當(dāng)?shù)耐綑C(jī)制。
2.死鎖:兩個(gè)或多個(gè)線程互相等待對(duì)方釋放資源。
解決方案:按固定順序獲取鎖,使用超時(shí),或避免嵌套鎖。
3.活鎖:線程不斷改變狀態(tài)以響應(yīng)其他線程,但無法取得進(jìn)展。
解決方案:引入隨機(jī)性,或使用退避策略。
4.內(nèi)存可見性:一個(gè)線程的修改對(duì)其他線程不可見。
解決方案:使用volatile關(guān)鍵字或適當(dāng)?shù)耐綑C(jī)制。
線程安全集合:
.NET提供了多種線程安全的集合類:
1.ConcurrentQueue<T>:線程安全隊(duì)列
2.ConcurrentStack<T>:線程安全棧
3.ConcurrentDictionary<TKey, TValue>:線程安全字典
4.ConcurrentBag<T>:無序集合
5.BlockingCollection<T>:支持邊界和阻塞的集合
var concurrentQueue = new ConcurrentQueue<int>();
// 多線程安全入隊(duì)
Parallel.For(0, 100, i => concurrentQueue.Enqueue(i));
// 多線程安全出隊(duì)
Parallel.For(0, 100, i =>
{
if (concurrentQueue.TryDequeue(out int item))
{
Console.WriteLine($"出隊(duì): {item}");
}
});
5.2 死鎖預(yù)防
死鎖的四個(gè)必要條件(Coffman條件):
- 互斥條件:資源一次只能由一個(gè)線程持有。
- 占有并等待:線程持有資源并等待其他資源。
- 非搶占條件:線程持有的資源不能被強(qiáng)制拿走。
- 循環(huán)等待:存在一個(gè)線程循環(huán)等待鏈。
預(yù)防死鎖的策略:
鎖順序:確保所有線程以相同的順序獲取鎖。
例如,總是先獲取鎖A再獲取鎖B。
鎖超時(shí):使用Monitor.TryEnter或類似機(jī)制設(shè)置超時(shí)。
if (Monitor.TryEnter(_lock, TimeSpan.FromSeconds(1)))
{
try { /* 臨界區(qū) */ }
finally { Monitor.Exit(_lock); }
}
避免嵌套鎖:盡量減少鎖的嵌套層次。
使用更高級(jí)的抽象:如Concurrent集合或不可變數(shù)據(jù)結(jié)構(gòu)。
死鎖檢測(cè):在復(fù)雜系統(tǒng)中實(shí)現(xiàn)死鎖檢測(cè)機(jī)制。
死鎖示例與解決:
// 死鎖示例
object lock1 = new object();
object lock2 = new object();
void Thread1()
{
lock (lock1)
{
Thread.Sleep(100);
lock (lock2) { /* ... */ }
}
}
void Thread2()
{
lock (lock2)
{
Thread.Sleep(100);
lock (lock1) { /* ... */ }
}
}
// 解決方案:固定鎖順序
void SafeThread1()
{
lock (lock1)
{
Thread.Sleep(100);
lock (lock2) { /* ... */ }
}
}
???????void SafeThread2()
{
lock (lock1) // 與Thread1相同的順序
{
Thread.Sleep(100);
lock (lock2) { /* ... */ }
}
}5.3 性能考量
多線程性能優(yōu)化的關(guān)鍵點(diǎn):
減少鎖競(jìng)爭(zhēng):
- 縮小鎖范圍
- 使用細(xì)粒度鎖
- 考慮無鎖數(shù)據(jù)結(jié)構(gòu)
避免虛假共享:
- 當(dāng)多個(gè)線程頻繁訪問同一緩存行中的不同變量時(shí)發(fā)生
- 解決方案:填充數(shù)據(jù)結(jié)構(gòu)或重新組織數(shù)據(jù)訪問模式
合理設(shè)置并行度:
- 使用Environment.ProcessorCount獲取CPU核心數(shù)
- 在Parallel.For等操作中設(shè)置MaxDegreeOfParallelism
異步I/O優(yōu)于線程池:
對(duì)于I/O密集型操作,使用真正的異步API而非線程池
選擇適當(dāng)?shù)募项愋停?/p>
- 單線程:List, Dictionary
- 多線程生產(chǎn)者-消費(fèi)者:ConcurrentQueue, BlockingCollection
- 并行處理:Partitioner, PLINQ
性能測(cè)量工具:
Stopwatch:測(cè)量代碼執(zhí)行時(shí)間
var sw = Stopwatch.StartNew();
// 被測(cè)代碼
sw.Stop();
Console.WriteLine($"耗時(shí): {sw.ElapsedMilliseconds}ms");
性能分析器:
- Visual Studio性能分析器
- PerfView
- dotTrace
并發(fā)可視化工具:
Visual Studio并發(fā)可視化工具
5.4 調(diào)試多線程應(yīng)用
多線程調(diào)試的挑戰(zhàn):
- 非確定性:?jiǎn)栴}可能難以重現(xiàn)
- 競(jìng)態(tài)條件:調(diào)試可能改變時(shí)序
- 復(fù)雜狀態(tài):多個(gè)線程交織執(zhí)行
調(diào)試技巧:
命名線程:
Thread worker = new Thread(WorkerMethod); worker.Name = "Worker Thread";
使用調(diào)試位置標(biāo)記:
Debug.WriteLine($"線程 {Thread.CurrentThread.Name} 進(jìn)入方法X");
Visual Studio調(diào)試功能:
- 并行堆棧窗口
- 并行任務(wù)窗口
- 線程窗口
- 條件斷點(diǎn)
日志記錄:
- 添加詳細(xì)日志,包括線程ID和時(shí)間戳
- 考慮使用結(jié)構(gòu)化日志系統(tǒng)(如Serilog)
簡(jiǎn)化重現(xiàn):
- 使用Thread.Sleep人為制造競(jìng)態(tài)條件
- 在測(cè)試中控制線程調(diào)度
常見調(diào)試場(chǎng)景:
死鎖檢測(cè):
- 暫停調(diào)試器并檢查所有線程的調(diào)用堆棧
- 查找互相等待的鎖
競(jìng)態(tài)條件:
- 添加詳細(xì)日志記錄共享狀態(tài)的訪問
- 使用斷點(diǎn)和條件斷點(diǎn)
內(nèi)存泄漏:
- 檢查長(zhǎng)時(shí)間運(yùn)行的線程是否持有對(duì)象引用
- 分析內(nèi)存轉(zhuǎn)儲(chǔ)
6. 實(shí)際案例分析
案例1:高性能日志處理器
需求:開發(fā)一個(gè)高性能日志處理器,能夠并發(fā)處理大量日志消息,并寫入文件系統(tǒng),同時(shí)不影響主應(yīng)用程序性能。
解決方案:
public class AsyncLogger : IDisposable
{
private readonly BlockingCollection<string> _logQueue = new BlockingCollection<string>(10000);
private readonly Task _processingTask;
private readonly StreamWriter _writer;
private readonly CancellationTokenSource _cts = new CancellationTokenSource();
public AsyncLogger(string filePath)
{
_writer = new StreamWriter(filePath, append: true);
_processingTask = Task.Run(ProcessLogs);
}
public void Log(string message)
{
if (!_logQueue.TryAdd($"{DateTime.UtcNow:o}: {message}"))
{
// 隊(duì)列已滿,可選擇丟棄或等待
_logQueue.Add(message); // 阻塞直到有空間
}
}
private async Task ProcessLogs()
{
try
{
foreach (var message in _logQueue.GetConsumingEnumerable(_cts.Token))
{
try
{
await _writer.WriteLineAsync(message);
// 定期刷新以提高性能
if (_logQueue.Count == 0)
{
await _writer.FlushAsync();
}
}
catch (Exception ex)
{
Debug.WriteLine($"日志寫入失敗: {ex.Message}");
}
}
}
catch (OperationCanceledException)
{
// 正常退出
}
// 最終刷新
await _writer.FlushAsync();
}
public void Dispose()
{
_logQueue.CompleteAdding();
_cts.Cancel();
_processingTask.Wait();
_writer.Dispose();
_cts.Dispose();
}
}
// 使用示例
using var logger = new AsyncLogger("app.log");
// 多線程記錄日志
Parallel.For(0, 100, i =>
{
logger.Log($"消息 {i} 來自線程 {Thread.CurrentThread.ManagedThreadId}");
});
設(shè)計(jì)要點(diǎn):
- 使用生產(chǎn)者-消費(fèi)者模式分離日志記錄和處理
- BlockingCollection提供線程安全隊(duì)列和阻塞語義
- 異步文件寫入提高I/O性能
- 定期刷新平衡性能和數(shù)據(jù)安全
- 優(yōu)雅關(guān)閉處理
案例2:并行數(shù)據(jù)處理管道
需求:處理大量數(shù)據(jù),需要經(jīng)過多個(gè)處理階段,每個(gè)階段可以并行化。
解決方案:
public class DataProcessingPipeline
{
public async Task ProcessDataAsync(IEnumerable<InputData> inputData)
{
// 階段1: 并行數(shù)據(jù)加載和初步處理
var stage1Results = inputData
.AsParallel()
.WithDegreeOfParallelism(Environment.ProcessorCount)
.Select(LoadAndPreprocessData)
.ToList();
// 階段2: 并行復(fù)雜計(jì)算
var stage2Tasks = stage1Results
.Select(data => Task.Run(() => ComputeComplexFeatures(data)))
.ToArray();
var stage2Results = await Task.WhenAll(stage2Tasks);
// 階段3: 并行驗(yàn)證和過濾
var stage3Results = new ConcurrentBag<ResultData>();
Parallel.ForEach(stage2Results, data =>
{
if (ValidateData(data))
{
var transformed = TransformData(data);
stage3Results.Add(transformed);
}
});
// 階段4: 批量存儲(chǔ)
await BatchStoreResultsAsync(stage3Results);
}
private InputData LoadAndPreprocessData(RawData raw)
{
// 模擬耗時(shí)操作
Thread.Sleep(10);
return new InputData();
}
private ComplexData ComputeComplexFeatures(InputData input)
{
// 模擬CPU密集型操作
Thread.Sleep(100);
return new ComplexData();
}
private bool ValidateData(ComplexData data)
{
// 簡(jiǎn)單驗(yàn)證
return true;
}
private ResultData TransformData(ComplexData data)
{
return new ResultData();
}
private async Task BatchStoreResultsAsync(IEnumerable<ResultData> results)
{
// 模擬批量存儲(chǔ)
await Task.Delay(100);
}
}設(shè)計(jì)要點(diǎn):
- 混合使用Parallel LINQ、Task和Parallel.ForEach
- 根據(jù)操作類型選擇最佳并行策略
- CPU密集型操作使用Parallel或PLINQ
- I/O操作使用異步Task
- 使用ConcurrentBag進(jìn)行線程安全收集
案例3:實(shí)時(shí)數(shù)據(jù)儀表板
需求:構(gòu)建一個(gè)實(shí)時(shí)數(shù)據(jù)儀表板,從多個(gè)數(shù)據(jù)源獲取數(shù)據(jù),更新UI,并確保UI保持響應(yīng)。
解決方案(WPF示例):
public class DashboardViewModel : INotifyPropertyChanged
{
private readonly CancellationTokenSource _cts = new CancellationTokenSource();
private readonly ObservableCollection<DataItem> _items = new ObservableCollection<DataItem>();
private readonly object _syncLock = new object();
private double _averageValue;
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<DataItem> Items => _items;
public double AverageValue
{
get => _averageValue;
private set
{
if (_averageValue != value)
{
_averageValue = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(AverageValue)));
}
}
}
public DashboardViewModel()
{
// 啟動(dòng)數(shù)據(jù)收集任務(wù)
Task.Run(() => CollectDataAsync(_cts.Token));
}
private async Task CollectDataAsync(CancellationToken ct)
{
var dataSources = new IDataSource[]
{
new NetworkDataSource(),
new FileDataSource(),
new DatabaseDataSource()
};
while (!ct.IsCancellationRequested)
{
try
{
// 并行從所有數(shù)據(jù)源獲取數(shù)據(jù)
var tasks = dataSources
.Select(ds => ds.GetDataAsync(ct))
.ToArray();
// 等待所有數(shù)據(jù)源響應(yīng)或超時(shí)
var timeoutTask = Task.Delay(TimeSpan.FromSeconds(5), ct);
var completedTask = await Task.WhenAny(
Task.WhenAll(tasks),
timeoutTask);
if (completedTask == timeoutTask)
{
Debug.WriteLine("數(shù)據(jù)獲取超時(shí)");
continue;
}
// 處理接收到的數(shù)據(jù)
var allData = tasks.Select(t => t.Result).ToList();
var newItems = ProcessData(allData);
// 更新UI線程上的集合
await Application.Current.Dispatcher.InvokeAsync(() =>
{
foreach (var item in newItems)
{
_items.Add(item);
}
// 保持合理數(shù)量的項(xiàng)目
while (_items.Count > 1000)
{
_items.RemoveAt(0);
}
// 更新平均值
AverageValue = _items.Average(i => i.Value);
}, System.Windows.Threading.DispatcherPriority.Background);
// 短暫延遲
await Task.Delay(TimeSpan.FromSeconds(1), ct);
}
catch (OperationCanceledException)
{
// 正常退出
break;
}
catch (Exception ex)
{
Debug.WriteLine($"數(shù)據(jù)收集錯(cuò)誤: {ex.Message}");
await Task.Delay(TimeSpan.FromSeconds(5), ct);
}
}
}
private List<DataItem> ProcessData(List<RawData[]> allData)
{
// 模擬數(shù)據(jù)處理
var result = new List<DataItem>();
foreach (var dataSet in allData)
{
foreach (var raw in dataSet)
{
result.Add(new DataItem
{
Timestamp = DateTime.Now,
Value = raw.Value * 1.2,
Source = raw.SourceName
});
}
}
return result;
}
public void Dispose()
{
_cts.Cancel();
_cts.Dispose();
}
}設(shè)計(jì)要點(diǎn):
- 使用async/await保持UI響應(yīng)
- 并行獲取多個(gè)數(shù)據(jù)源
- 使用Dispatcher在UI線程上更新控件
- 實(shí)現(xiàn)超時(shí)處理增加魯棒性
- 使用CancellationToken支持優(yōu)雅關(guān)閉
- 后臺(tái)優(yōu)先級(jí)更新減少UI卡頓
7. 結(jié)論
.NET平臺(tái)提供了豐富的多線程編程模型和API,從低級(jí)的Thread類到高級(jí)的async/await模式,涵蓋了各種并發(fā)編程場(chǎng)景的需求。通過本文的全面分析,我們可以得出以下關(guān)鍵結(jié)論:
技術(shù)選擇應(yīng)根據(jù)具體需求:
- 對(duì)于簡(jiǎn)單的后臺(tái)任務(wù),ThreadPool或Task.Run足夠
- 對(duì)于數(shù)據(jù)并行操作,Parallel類或PLINQ更合適
- 對(duì)于I/O密集型異步操作,async/await是最佳選擇
- 對(duì)于需要精細(xì)控制的場(chǎng)景,可以直接使用Thread
線程同步至關(guān)重要:
- 理解各種同步原語的適用場(chǎng)景
- 最小化同步范圍以提高性能
- 注意死鎖預(yù)防和線程安全問題
現(xiàn)代異步模式優(yōu)勢(shì)明顯:
- async/await提供了更簡(jiǎn)潔、可維護(hù)的代碼
- 與Task Parallel Library良好集成
- 特別適合I/O密集型操作和UI應(yīng)用程序
性能與正確性平衡:
- 并行化不一定總能提高性能(考慮Amdahl定律)
- 測(cè)量、分析、優(yōu)化是性能調(diào)優(yōu)的關(guān)鍵步驟
- 正確性應(yīng)始終優(yōu)先于性能優(yōu)化
調(diào)試與測(cè)試挑戰(zhàn):
- 多線程應(yīng)用需要特別的調(diào)試技巧
- 編寫可測(cè)試的并發(fā)代碼
- 日志記錄是診斷并發(fā)問題的重要工具
隨著.NET平臺(tái)的持續(xù)發(fā)展,多線程和異步編程模型也在不斷演進(jìn)。開發(fā)人員應(yīng)當(dāng):
- 掌握基礎(chǔ)概念和原理
- 了解各種技術(shù)的適用場(chǎng)景和限制
- 遵循最佳實(shí)踐編寫健壯的并發(fā)代碼
- 持續(xù)學(xué)習(xí)新的語言特性和框架改進(jìn)
通過合理選擇和組合本文介紹的各種多線程實(shí)現(xiàn)方法和同步技術(shù),.NET開發(fā)人員可以構(gòu)建出高性能、高響應(yīng)性且可靠的并發(fā)應(yīng)用程序。
以上就是.NET中多線程任務(wù)實(shí)現(xiàn)的幾種方法小結(jié)的詳細(xì)內(nèi)容,更多關(guān)于.NET多線程任務(wù)實(shí)現(xiàn)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
ASP.NET實(shí)現(xiàn)數(shù)據(jù)的添加(第10節(jié))
這篇文章主要介紹了ASP.NET如何實(shí)現(xiàn)數(shù)據(jù)的添加,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2015-08-08
ASP.NET Core Authentication認(rèn)證實(shí)現(xiàn)方法
這篇文章主要介紹了ASP.NET Core Authentication認(rèn)證實(shí)現(xiàn)方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08
ASP.NET 頁(yè)面刷新的實(shí)現(xiàn)方法(包括html,js)
ASP.NET 頁(yè)面刷新的實(shí)現(xiàn)方法,比較全了, 包括html與js下的實(shí)現(xiàn)方法。2010-03-03
asp.net后臺(tái)動(dòng)態(tài)添加JS文件和css文件的引用實(shí)現(xiàn)方法
這篇文章主要介紹了asp.net后臺(tái)動(dòng)態(tài)添加JS文件和css文件的引用實(shí)現(xiàn)方法,是非常簡(jiǎn)單實(shí)用的技巧,需要的朋友可以參考下2014-09-09
Automation服務(wù)器不能創(chuàng)建對(duì)象的多種解決辦法
這篇文章主要介紹了Automation服務(wù)器不能創(chuàng)建對(duì)象的多種解決辦法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-01-01
asp.net中幾種常用的身份驗(yàn)證方法總結(jié)
本篇文章小編就為大家介紹一下在asp.net中幾種常用的身份驗(yàn)證方法。需要的朋友可以過來參考下,希望對(duì)大家有所幫助2013-11-11

