Document

.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 中已被明确弃用

现代开发应使用 ManualResetEventAutoResetEvent 或 CancellationToken 实现线程同步与控制。

2.2.3 终止线程

Thread.Abort() 方法曾用于强制终止线程,但同样因资源泄漏和状态不一致问题被弃用。在 .NET Core 中,Abort() 不再可用

正确的做法是通过协作式取消(Cooperative Cancellation)机制,即让线程定期检查取消令牌(CancellationToken),并在收到信号时优雅退出。

 

CancellationTokenSource cts new CancellationTokenSource();Thread worker new Thread(() =>{ 
  for (int 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 枚举:

  • Lowest
  • BelowNormal
  • Normal(默认)
  • AboveNormal
  • Highest

设置示例:

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),它将帮助我们更高效地管理并发任务。

 





 

posted @ 2025-09-22 09:53  从未被超越  阅读(15)  评论(0)    收藏  举报