September 19, 2023
By: Kevin
C#线程池/JIT/进程的一些使用方法
获取正在使用的线程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
workerThreads 和 completionPortThreads都是线程池(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的线程池能够更有效地管理不同类型的任务, 并确保即使在高负载情况下也能保持高性能和响应能力.