September 19, 2023
By: Kevin

C#线程池/JIT/进程的一些使用方法

  1. 获取正在使用的线程count
    1. 当前进程中的线程数
    2. 默认线程池中的线程数
  2. 取消Task.Run()
    1. 结束一个线程, 个线程会被重用
    2. 使用一个token, 取消多个线程
  3. 启动子进程
  4. 获得进程id, 根据id再获取进程
  5. 对象序列化为json的时间消耗
  6. 多个Task的延时
  7. 线程池

获取正在使用的线程count

当前进程中的线程数

using System;
using System.Diagnostics;
Process currentProcess = Process.GetCurrentProcess();
Console.WriteLine("Thread Count: " + currentProcess.Threads.Count);
: Thread Count: 19

默认线程池中的线程数

using System.Threading;
_ = Task.Run(() => Thread.Sleep(1000));
ThreadPool.GetMaxThreads(out int workerThreads, out int completionPortThreads);
ThreadPool.GetAvailableThreads(out int availableWorkerThreads, out int availableCompletionPortThreads);

int inUseWorkerThreads = workerThreads - availableWorkerThreads;

Console.WriteLine("Total Worker Threads: " + workerThreads);
Console.WriteLine("Available Worker Threads: " + availableWorkerThreads);
Console.WriteLine("In Use Worker Threads: " + inUseWorkerThreads);
: Total Worker Threads: 32767
: Available Worker Threads: 32766
: In Use Worker Threads: 1

取消Task.Run()

结束一个线程, 个线程会被重用

  using System;
  using System.Threading;
  using System.Threading.Tasks;

  Console.WriteLine($"程序开始, 在线程 {Thread.CurrentThread.ManagedThreadId}");

  // 创建CancellationTokenSource
  var cts = new CancellationTokenSource();
  CancellationToken token = cts.Token;

  // 使用Task.Run启动异步操作
  var task = Task.Run(() => DoWork(token), token);

  // 等待一段时间, 然后取消任务
  Thread.Sleep(200);
  cts.Cancel();
  Thread.Sleep(200);
  _ = Task.Run(() =>
        Console.WriteLine($"新任务开始, 在线程 {Thread.CurrentThread.ManagedThreadId}")
  );
  _ = Task.Run(() =>
        Console.WriteLine($"新任务开始, 在线程 {Thread.CurrentThread.ManagedThreadId}")
  );

  static void DoWork(CancellationToken cancellationToken)
  {
      Console.WriteLine($"任务开始, 在线程 {Thread.CurrentThread.ManagedThreadId}");
      for (int i = 0; i < 5; i++)
      {
          // 检查是否取消
          if (cancellationToken.IsCancellationRequested)
          {
              Console.WriteLine("任务取消");
              return;
          }

          // 模拟工作
          Console.WriteLine($"工作进度: {i + 1}/5");
          Thread.Sleep(100);
      }

      Console.WriteLine("任务完成");
  }

Thread.Sleep(2000);
: 程序开始, 在线程 1
: 任务开始, 在线程 11
: 工作进度: 1/5
: 工作进度: 2/5
: 工作进度: 3/5
: 任务取消
: 新任务开始, 在线程 11
: 新任务开始, 在线程 5

使用一个token, 取消多个线程

using System.Threading;
using System.Threading.Tasks;


var cts = new CancellationTokenSource();
CancellationToken token = cts.Token;

// 启动多个任务, 共用同一个CancellationToken
var task1 = Task.Run(() => DoWork("Task 1", token), token);
var task2 = Task.Run(() => DoWork("Task 2", token), token);
var task3 = Task.Run(() => DoWork("Task 3", token), token);

// 模拟等待, 然后取消所有任务
Thread.Sleep(200);
cts.Cancel();

Task.WaitAll(task1, task2, task3);

static void DoWork(string taskName, CancellationToken cancellationToken)
{
    for (int i = 0; i < 5; i++)
    {
        if (cancellationToken.IsCancellationRequested)
        {
            Console.WriteLine($"{taskName} 取消");
            return;
        }

        Console.WriteLine($"{taskName} 进度: {i + 1}/5");
        Thread.Sleep(100);
    }

    Console.WriteLine($"{taskName} 完成");
}
: Task 3 进度: 1/5
: Task 1 进度: 1/5
: Task 2 进度: 1/5
: Task 3 进度: 2/5
: Task 1 进度: 2/5
: Task 2 进度: 2/5
: Task 2 取消
: Task 1 取消
: Task 3 取消

启动子进程


// 设置启动参数
ProcessStartInfo psi = new ProcessStartInfo();
psi.FileName = "/bin/ls";  // 子进程的可执行文件路径
psi.UseShellExecute = false;
psi.RedirectStandardOutput = true;
psi.RedirectStandardError = true;

// 创建并启动进程
Process process = new Process();
process.StartInfo = psi;

// 事件处理, 例如捕获输出等
process.OutputDataReceived += (sender, e) => { Console.WriteLine("Output: " + e.Data); };
process.ErrorDataReceived += (sender, e) => { Console.WriteLine("Error: " + e.Data); };

// 启动
process.Start();

// 开始异步读取输出
process.BeginOutputReadLine();
process.BeginErrorReadLine();

// 等待进程结束
process.WaitForExit();
Output: DeploymentReport.txt
Output: PlayGround
Output: WriteText.txt
Error:
Output:

获得进程id, 根据id再获取进程

using System.Diagnostics;
 Process currentProcess = Process.GetCurrentProcess();
// 获取当前进程的ID
int processId = currentProcess.Id;
// 通过id获得进程信息
var p = Process.GetProcessById(processId);
// 输出当前进程ID
Console.WriteLine($"当前进程ID: {processId}");
Console.WriteLine($"当前进程ID: {p.Id}");
: 当前进程ID: 5496
: 当前进程ID: 5496

对象序列化为json的时间消耗

JIT的时间使用

和JVM一样, 只有代码在JIT路径上, 才会获得极快的执行速度, 预热之后10000次执行的比1次还快.

using System;
using System.Text.Json;
using System.Threading;
using System.Text.Json.Serialization;
using System.Diagnostics;

public record A(string a);

var a = new A("haha");
var b = new A("xixi");
void f()
{
    for (var i = 0; i < 1; i++)
    {
        JsonSerializer.Serialize(a);
    }
}

void f1()
{
    for (var i = 0; i < 10000; i++)
    {
        JsonSerializer.Serialize(b);
    }
}
Stopwatch stopwatch = new Stopwatch();
Stopwatch stopwatch1 = new Stopwatch();

await Task.Run(() =>
{
    stopwatch.Start();
    f();
    stopwatch.Stop();
    Console.WriteLine($"执行时间1: {stopwatch.Elapsed.TotalMilliseconds} ms");
});



Thread.Sleep(1000);

await Task.Run(() =>
{
    stopwatch1.Start();
    f1();
    stopwatch1.Stop();
    Console.WriteLine($"执行时间2: {stopwatch1.Elapsed.TotalMilliseconds} ms");
});
: 执行时间1: 7.0743 ms
: 执行时间2: 9.0795 ms

多个Task的延时

突然创建大量新的线程, 可能会有一定的延迟. 你可以使用ThreadPool.SetMinThreads来预先设置一个较高的线程数.

降低创建线程的延时. ThreadPool.SetMinThreads(50, 50);预热线程

using System.Threading;
//ThreadPool.SetMinThreads(50, 50);
public void f ()
{
    Task.Run(() => {

        DateTime currentTime = DateTime.Now;
        Console.WriteLine($"当前时间(精确到毫秒): {currentTime.ToString("yyyy-MM-dd HH:mm:ss.fff")} 线程 {Thread.CurrentThread.ManagedThreadId}");});
        Thread.Sleep(100);
}
for(int i = 0; i < 50 ; i++){
    f();

}
await Task.Delay(5000);
当前时间(精确到毫秒): 2023-09-20 08:44:58.517 线程 53
当前时间(精确到毫秒): 2023-09-20 08:44:58.517 线程 37
当前时间(精确到毫秒): 2023-09-20 08:44:58.517 线程 31
当前时间(精确到毫秒): 2023-09-20 08:44:58.517 线程 44
当前时间(精确到毫秒): 2023-09-20 08:44:58.517 线程 38
当前时间(精确到毫秒): 2023-09-20 08:44:58.517 线程 17
当前时间(精确到毫秒): 2023-09-20 08:44:58.517 线程 52
当前时间(精确到毫秒): 2023-09-20 08:44:58.517 线程 28
当前时间(精确到毫秒): 2023-09-20 08:44:58.517 线程 27
当前时间(精确到毫秒): 2023-09-20 08:44:58.517 线程 55
当前时间(精确到毫秒): 2023-09-20 08:44:58.517 线程 51
当前时间(精确到毫秒): 2023-09-20 08:44:58.522 线程 8
当前时间(精确到毫秒): 2023-09-20 08:44:58.522 线程 16
当前时间(精确到毫秒): 2023-09-20 08:44:58.522 线程 13
当前时间(精确到毫秒): 2023-09-20 08:44:58.522 线程 48
当前时间(精确到毫秒): 2023-09-20 08:44:58.522 线程 5
当前时间(精确到毫秒): 2023-09-20 08:44:58.522 线程 26
当前时间(精确到毫秒): 2023-09-20 08:44:58.522 线程 50
当前时间(精确到毫秒): 2023-09-20 08:44:58.522 线程 32
当前时间(精确到毫秒): 2023-09-20 08:44:58.561 线程 14
当前时间(精确到毫秒): 2023-09-20 08:44:58.561 线程 9
当前时间(精确到毫秒): 2023-09-20 08:44:58.561 线程 10
当前时间(精确到毫秒): 2023-09-20 08:44:58.561 线程 25
当前时间(精确到毫秒): 2023-09-20 08:44:58.561 线程 20
当前时间(精确到毫秒): 2023-09-20 08:44:58.561 线程 30
当前时间(精确到毫秒): 2023-09-20 08:44:58.561 线程 12
当前时间(精确到毫秒): 2023-09-20 08:44:58.561 线程 41
当前时间(精确到毫秒): 2023-09-20 08:44:58.561 线程 39
当前时间(精确到毫秒): 2023-09-20 08:44:58.561 线程 42
当前时间(精确到毫秒): 2023-09-20 08:44:58.561 线程 35
当前时间(精确到毫秒): 2023-09-20 08:44:58.561 线程 33
当前时间(精确到毫秒): 2023-09-20 08:44:58.561 线程 47
当前时间(精确到毫秒): 2023-09-20 08:44:58.561 线程 43
当前时间(精确到毫秒): 2023-09-20 08:44:58.561 线程 54
当前时间(精确到毫秒): 2023-09-20 08:44:58.561 线程 45
当前时间(精确到毫秒): 2023-09-20 08:44:58.561 线程 22
当前时间(精确到毫秒): 2023-09-20 08:44:58.561 线程 36
当前时间(精确到毫秒): 2023-09-20 08:44:58.561 线程 7
当前时间(精确到毫秒): 2023-09-20 08:44:58.561 线程 21
当前时间(精确到毫秒): 2023-09-20 08:44:58.561 线程 46
当前时间(精确到毫秒): 2023-09-20 08:44:58.561 线程 49
当前时间(精确到毫秒): 2023-09-20 08:44:58.561 线程 15
当前时间(精确到毫秒): 2023-09-20 08:44:58.561 线程 56
当前时间(精确到毫秒): 2023-09-20 08:44:58.561 线程 18
当前时间(精确到毫秒): 2023-09-20 08:44:58.561 线程 40
当前时间(精确到毫秒): 2023-09-20 08:44:58.561 线程 19
当前时间(精确到毫秒): 2023-09-20 08:44:58.561 线程 29
当前时间(精确到毫秒): 2023-09-20 08:44:58.561 线程 23
当前时间(精确到毫秒): 2023-09-20 08:44:58.561 线程 24
当前时间(精确到毫秒): 2023-09-20 08:44:58.561 线程 34

线程池

Task.Run 在 .NET中并不是每次都会创建一个新的线程. 它使用的是线程池(ThreadPool)中的线程来执行任务.

线程池是一种线程使用优化机制, 它维护了一个线程集合, 这些线程可以被多个任务重用, 以减少线程创建和销毁的开销.

Task.Run 使用的就是 .NET的线程池(ThreadPool)来调度和执行任务. 我们可以查看一下当前系统的线程池大小

using System.Threading;

int workerThreads, completionPortThreads;
ThreadPool.GetMaxThreads(out workerThreads, out completionPortThreads);
Console.WriteLine($"Max worker threads: {workerThreads}, Max completion port threads: {completionPortThreads}");

ThreadPool.GetMinThreads(out workerThreads, out completionPortThreads);
Console.WriteLine($"Min worker threads: {workerThreads}, Min completion port threads: {completionPortThreads}");

_ = Task.Run(() => {
     int currentThreadId = Thread.CurrentThread.ManagedThreadId;
     Console.WriteLine($"Current Thread ID: {currentThreadId}");
});

_ = Task.Run(() => {
     int currentThreadId = Thread.CurrentThread.ManagedThreadId;
     Console.WriteLine($"Current Thread ID: {currentThreadId}");
});

_ = Task.Run(() => {
     int currentThreadId = Thread.CurrentThread.ManagedThreadId;
     Console.WriteLine($"Current Thread ID: {currentThreadId}");
});
: Max worker threads: 32767, Max completion port threads: 1000
: Min worker threads: 8, Min completion port threads: 8
: Current Thread ID: 8
: Current Thread ID: 5
: Current Thread ID: 7

workerThreadscompletionPortThreads都是线程池(ThreadPool)中的线程, 但它们各自有不同的用途和特点:

workerThreads(工作线程)

  • 用途: 主要用于执行计算密集型或其他需要 CPU时间的任务.
  • 调度: 当你使用 ThreadPool.QueueUserWorkItem 或 Task.Run等方法提交一个任务时, 这个任务通常会由一个工作线程来执行.
  • 特点: 工作线程通常用于执行那些不涉及 I/O 操作或者其他会阻塞线程的任务.

completionPortThreads(I/O 完成端口线程)

  • 用途: 专门用于处理异步 I/O 操作的完成通知.
  • 调度: 当一个异步 I/O 操作完成时, 与之关联的 I/O 完成端口会通知一个 I/O 完成端口线程.
  • 特点: 这些线程通常在等待 I/O操作完成的通知时是被阻塞的, 一旦收到通知, 它们就会快速地处理相关任务.

两者的关系

  • 分工合作: workerThreads 主要负责计算密集型任务, 而completionPortThreads 负责 I/O密集型任务. 这样的分工有助于提高应用程序的整体性能.
  • 独立调度: 这两种线程是独立调度的, 即使所有工作线程都在忙, I/O完成端口线程仍然可以及时响应 I/O 事件.
  • 共享资源: 虽然它们有不同的用途, 但它们都是线程池的一部分, 因此共享相同的线程管理和调度机制.

通过这种方式, .NET的线程池能够更有效地管理不同类型的任务, 并确保即使在高负载情况下也能保持高性能和响应能力.

Tags: c# .NET