您的当前位置:首页正文

C#--关于Task.Run() 线程机制解析

2024-12-12 来源:个人技术集锦

释义:代码中使用了 Task.Run(),看起来像是启动了很多线程,但实际上并不是每次都会创建那么多线程。在 C# 中,Task.Run() 会将任务交给 线程池 来处理,而不是每个任务都直接创建一个新的线程。

1.线程池的工作原理:

2.具体到代码:

for (int j = i; j < i + batchSize && j < 10000; j++) 
{ int index = j; // 捕获图片任务的索引 
captureTasks.Add(Task.Run(() => CaptureAndSavePhoto(Path.Combine(folderPath, $"photo_{index + 1}.jpg")))); 
}

在这个循环中,每次都调用了 Task.Run(),它会将任务提交给线程池。假设 batchSize 是 500,那么实际上并不是立刻启动 500 个线程,而是启动 500 个任务,这些任务会在线程池中排队,并由线程池中的可用线程来执行。

  • 线程池会根据可用的线程数来调度这些任务。默认情况下,线程池会限制最大线程数,通常会根据系统资源和硬件自动调整。并不是每个 Task.Run() 都创建一个新的操作系统线程,而是使用现有的线程池中的线程来执行任务。

  • 如果线程池中有空闲线程,它就会立刻执行任务。如果没有空闲线程,任务会排队等待,直到有线程空闲出来。

3.为什么使用线程池而不是直接创建线程?

  • 性能优化:创建线程是非常昂贵的操作,而线程池通过重用线程来避免了频繁的线程创建和销毁。这样可以显著提升程序的性能和响应速度。

  • 资源管理:线程池自动管理线程的生命周期,避免了线程过多导致的资源浪费或系统负载过高的情况。

控制并发量:

虽然线程池能够管理线程的创建,但如果担心并发过高,也可以通过限制并发的任务数量来避免线程池被过度使用。例如,可以使用 SemaphoreSlimTask.WhenAny 来控制同时运行的任务数量,确保不会超出一定的并发量。

例如,使用 SemaphoreSlim 来限制并发:

SemaphoreSlim semaphore = new SemaphoreSlim(50);  // 限制最多50个并发任务
for (int i = 0; i < 10000; i += batchSize)
{
    List<Task> captureTasks = new List<Task>();

    for (int j = i; j < i + batchSize && j < 10000; j++)
    {
        int index = j;  // 捕获图片任务的索引
        await semaphore.WaitAsync();  // 等待一个可用的“槽”
        
        captureTasks.Add(Task.Run(async () =>
        {
            try
            {
                await CaptureAndSavePhoto(Path.Combine(folderPath, $"photo_{index + 1}.jpg"));
            }
            finally
            {
                semaphore.Release();  // 完成任务后释放“槽”
            }
        }));
    }

    // 等待当前批次的所有图像处理完成
    await Task.WhenAll(captureTasks);
    Console.WriteLine($"Captured {i + batchSize} photos.");
}

这个代码的作用是,最多同时只有 50 个任务在运行,超出数量的任务会等待,直到有任务完成并释放出一个“槽”。

总结:

所以,Task.Run() 并不是每次都启动一个新线程,它会将任务提交到线程池,由线程池来调度执行。线程池会控制并发数量,避免过多的线程竞争资源。如果需要,你可以通过手动限制并发量来避免过高的并发数。

显示全文