.Net Core 任务并行库(TPL)深度解析:Task 与 Parallel 的高效应用
访问链接:https://mp.weixin.qq.com/s/usatzDu5BmNS75UFloW7cA
.NET Core 中的线程基础
在 .NET Core 的并发编程体系中,线程(Thread)是最基础、最底层的执行单元。尽管现代开发更多依赖于高级抽象如 Task 和 async/await,但理解原始线程的工作机制对于深入掌握并发原理、优化性能以及排查复杂问题至关重要。本章将系统讲解 System.Threading.Thread 类的核心功能、线程生命周期管理、前台与后台线程的区别,并通过一个实际的日志记录器案例,展示线程在项目中的典型应用。
2.1 System.Threading.Thread 类详解
在 .NET Core 中,Thread 类位于 System.Threading 命名空间,代表操作系统级别的线程。它允许开发者显式创建和控制线程的执行。
创建线程的基本方式:
using System;using System.Threading;class Program{
static void Main()
{
// 方式一:使用 ThreadStart 委托
Thread thread1 = new Thread(new ThreadStart(WorkerMethod));
thread1.Start();
// 方式二:使用 ParameterizedThreadStart(可传递参数)
Thread thread2 = new Thread(new ParameterizedThreadStart(WorkerMethodWithParam));
thread2.Start("Hello from thread!");
Console.WriteLine("主线程继续执行...");
Console.ReadKey();
}
static void WorkerMethod()
{
Console.WriteLine($"工作线程 {Thread.CurrentThread.ManagedThreadId} 正在运行。");
Thread.Sleep(2000); // 模拟耗时操作
Console.WriteLine("工作线程执行完毕。");
}
static void WorkerMethodWithParam(object data)
{
Console.WriteLine($"参数线程接收到数据: {data}");
}}
关键点说明:
ThreadStart用于无参方法。ParameterizedThreadStart允许传入一个object类型的参数,但类型安全较差,建议在现代代码中优先使用Task或闭包。Thread.CurrentThread提供对当前执行线程的引用,常用于获取线程 ID、名称或设置优先级。
2.2 线程的生命周期:创建、启动、挂起、终止
线程在其生命周期中会经历多个状态:未启动(Unstarted)、运行中(Running)、等待(WaitSleepJoin)、已停止(Stopped)等。
2.2.1 启动与等待
调用 Start() 方法后,线程进入就绪状态,等待 CPU 调度。主线程可通过 Join() 方法阻塞,直到目标线程完成。
Thread worker = new Thread(() =>{
Console.WriteLine("子线程开始...");
Thread.Sleep(3000);
Console.WriteLine("子线程结束。");});worker.Start();Console.WriteLine("主线程等待子线程...");worker.Join();// 阻塞主线程,直到 worker 完成
Console.WriteLine("主线程继续。");
注意:Join() 可带超时参数(如 Join(5000)),避免无限等待。
2.2.2 挂起与恢复(已过时)
Thread.Suspend() 和 Thread.Resume() 曾用于暂停和恢复线程,但由于极易导致死锁(例如在锁内部被挂起),
在 .NET Core 中已被明确弃用。
现代开发应使用 ManualResetEvent、AutoResetEvent 或 CancellationToken 实现线程同步与控制。
2.2.3 终止线程
Thread.Abort() 方法曾用于强制终止线程,但同样因资源泄漏和状态不一致问题被弃用。在 .NET Core 中,Abort() 不再可用。
正确的做法是通过协作式取消(Cooperative Cancellation)机制,即让线程定期检查取消令牌(CancellationToken),并在收到信号时优雅退出。
CancellationTokenSource cts = new CancellationTokenSource();Thread worker = new Thread(() =>{
for (int i = 0; i < 100; i++)
{
if (cts.Token.IsCancellationRequested){
Console.WriteLine("线程收到取消请求,正在退出...");return;
}
Console.WriteLine($"工作项 {i}");Thread.Sleep(100);
}});worker.Start();Thread.Sleep(1000);cts.Cancel(); // 请求取消worker.Join(); // 等待线程安全退出
2.3 线程优先级与调度
线程的优先级影响操作系统调度器分配 CPU 时间片的频率。.NET Core 提供了 ThreadPriority 枚举:
LowestBelowNormalNormal(默认)AboveNormalHighest
设置示例:
Thread highPriorityThread = new Thread(() =>{
Console.WriteLine("高优先级线程运行中...");});highPriorityThread.Priority = ThreadPriority.AboveNormal;highPriorityThread.Start();
⚠️ 警告:滥用高优先级可能导致“线程饥饿”(低优先级线程长期得不到执行),应谨慎使用,仅在必要时提升关键任务的优先级。
2.4 前台线程 vs 后台线程
这是 .NET 中一个至关重要的概念,直接影响应用程序的生命周期。
- 前台线程(Foreground Thread):只要有一个前台线程在运行,应用程序进程就不会退出。
- 后台线程(Background Thread):当所有前台线程结束后,即使后台线程仍在运行,进程也会被终止。
示例:
static void Main(){Thread foreground = new Thread(() =>{Thread.Sleep(5000);Console.WriteLine("前台线程完成。");});Thread background = new Thread(() =>{while (true){Console.WriteLine("后台线程运行中...");Thread.Sleep(1000);}});background.IsBackground = true; // 设置为后台线程foreground.Start();background.Start();Console.WriteLine("主线程结束。");// 程序将在 foreground 线程结束后立即退出,background 线程被强制终止}
最佳实践:
- 长时间运行的辅助任务(如日志写入、心跳检测)应使用后台线程。
- 关键业务逻辑应使用前台线程,确保任务完成。
- 在控制台应用或 Windows 服务中,需特别注意线程类型,避免进程提前退出。
9
2.5 实践案例:多线程日志记录器设计
在高并发系统中,频繁的磁盘 I/O 操作会成为性能瓶颈。通过将日志写入操作放入独立线程,可以避免阻塞主业务逻辑。
需求:
- 支持异步写入日志
- 线程安全(多线程环境下不冲突)
- 可配置日志级别
- 支持优雅关闭
实现:
using System;using System.Collections.Concurrent;using
System.IO;using System.Threading;public class AsyncLogger :
IDisposable{
private readonly ConcurrentQueue<string> _logQueue = new ConcurrentQueue<string>();
private readonly Thread _logThread;
private readonly CancellationTokenSource _cts = new CancellationTokenSource();
private readonly string _logFilePath;
private volatile bool _isRunning = true;
public enum LogLevel { Info, Warning, Error }
public AsyncLogger(string logFilePath){
_logFilePath = logFilePath;_logThread = new Thread(WriteLog) { IsBackground = true };
_logThread.Start();}public void Log(LogLevel level, string message){
string logEntry = $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] [{level}] {message}";
_logQueue.Enqueue(logEntry);}
private void WriteLog(){
while (_isRunning || !_logQueue.IsEmpty)
{
if (_logQueue.TryDequeue(out string logEntry))
{
try{
File.AppendAllText(_logFilePath, logEntry + Environment.NewLine);
}catch (Exception ex)
{
// 实际项目中应有更完善的错误处理(如写入备用日志)
Console.WriteLine($"日志写入失败: {ex.Message}");
}
}
else{
Thread.Sleep(10); // 短暂休眠,避免 CPU 空转
}
}
}
public void Dispose(){
_isRunning = false;
_logThread.Join(5000); // 最多等待 5 秒让日志写完
_cts.Dispose();}}
使用示例:
using (var logger = new AsyncLogger("app.log")){
Parallel.For(0, 100, i =>{
logger.Log(AsyncLogger.LogLevel.Info, $"处理任务 {i}");
});}// 确保所有日志写入完成后再退出Console.WriteLine("日志记录完成。");
设计要点:
- 使用
ConcurrentQueue<T>确保线程安全的队列操作。 _isRunning标志和Join()实现优雅关闭。volatile关键字确保_isRunning的修改对所有线程可见。- 背景线程避免阻塞主线程。
小结
本章深入探讨了 .NET Core 中线程的基础知识,包括 Thread 类的使用、生命周期管理、优先级设置以及前台/后台线程的区别。
通过一个实用的异步日志记录器案例,我们展示了如何将线程应用于实际项目,提升系统响应性和稳定性。
然而,直接操作线程的方式在现代开发中已逐渐被更高层次的抽象(如 Task)所取代。在下一章中,我们将介绍 .NET Core 中更强大、
更安全的并发编程模型——任务并行库(TPL),它将帮助我们更高效地管理并发任务。

浙公网安备 33010602011771号