如何确保 Task.Factory.StartNew 不会减慢主线程的速度

本文关键字:线程 速度 StartNew 何确保 确保 Factory Task | 更新日期: 2023-09-27 18:31:37

有一堆压缩的数据块必须异步压缩 - 不会阻塞或减慢任何形状或形式的主线程

解压缩的块将在解压缩后立即由主线程使用。

目前我是这样做的:

foreach (var chunkPair in compressedChunkData)
{                        
    var task = Task.Factory.StartNew<Chunk>(() =>
    {
        var compressedBytes = Convert.FromBase64String(chunkPair.Value);
        var chunk = Decompress(compressedBytes);
        return chunk;
    }).ContinueWith((finishedTask) =>
    {
        var chunk = finishedTask.Result;
        TaskFinishActions.Enqueue(() =>
        {
            chunk.PostSerialize();
            document.Chunks.Add(chunkPair.Key, chunk);
        });
    });
}
// By the time we get here 20ms has passed!!!

问题是这似乎劫持了主线程正在运行的核心,这破坏了性能。

有没有办法让TaskFactory每个内核的线程和上下文仅在主线程被阻塞的那些短暂时刻才从主线程切换出去?

编辑:foreach 循环并不是代码中唯一变慢的部分,只要有大量的解压缩任务正在运行,主线程就会显着变慢

EDIT2:要解压缩的新数据一直到达,循环不会只运行一次:

  • 假设您有 250 件物品首先到达compressedChunkData
  • 下一帧你有 10 个项目,下一个 12 个,下一个 0 个,下一个 2 个,依此类推。

如何确保 Task.Factory.StartNew 不会减慢主线程的速度

可以使用将线程优先级设置为较低值的自定义TaskScheduler。Windows 始终首先调度优先级较高的线程。

也许您需要为任务设置到期日期,以便它们不会排队太多。听起来您需要低延迟处理。每个任务都可以检查它是否在 N 秒前计划了第一个操作,如果是,则立即退出。

另一种设计是生产者/使用者方案,其中低优先级线程承担工作。鉴于您的要求,我认为没有必要这样做,但这是一个更灵活的解决方案。创建数百个任务不是问题。每个任务只是一个小的内存数据结构。任务 != 线程。

您是否担心 for 循环变慢或循环后面的代码运行缓慢?

如果您担心 for 循环,那么有一个简单的解决方案,无论如何您都应该遵循。可以提供 ParallelOptions 类的实例来控制并发程度

Parallel.ForEach(compressedChunkData, chunkPair => {
    var compressedBytes = Convert.FromBase64String(chunkPair.Value);
    var chunk = Decompress(compressedBytes);
    TaskFinishActions.Enqueue(() => {
        chunk.PostSerialize();
        document.Chunks.Add(chunkPair.Key, chunk);
    });
});

如果你担心循环后会减慢代码速度,那么看看Jon Skeet的这个答案。从本质上讲,您应该使用asyncawait,或者在单独的任务上启动Parallel.Foreach

编辑:

让我们先弄清楚这一点:在像Windows这样的操作系统上,没有为线程或进程保留CPU这样的事情。它适用于时间切片计划)。因此,即使您的解压缩线程可能不会阻塞您的主线程,它仍然可能由于其他进程上的 CPU 密集型活动而被阻塞。将我们的偏好传达给操作系统的方法是使用优先级和 CPU 关联。

还有其他方法需要更多的手动控制,因此需要更多的工作。

  1. 也许您应该有一个单独的解压缩过程,并使用进程优先级和 CPU 关联来告诉操作系统它想要处理哪些内核。
  2. 您可以创建一个调度程序类来管理 RequestQueue(生产者-消费者)。每个解压缩请求都应在单个线程(将分配给单个逻辑 CPU)上进行管理。确保调度程序使用不超过 (TOTAL_CPUS-1)(保持一个可用于主线程)