【问题标题】:How to ensure that Task.Factory.StartNew doesn't slow down the main thread?如何确保 Task.Factory.StartNew 不会减慢主线程的速度?
【发布时间】:2016-01-25 22:01:50
【问题描述】:

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

解压后的块将被主线程使用。

目前我是这样做的:

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 个,等等。

【问题讨论】:

  • 您可以针对您的问题将主线程优先级设置得尽可能高。您正在运行forach,该循环的 20 毫秒不是正常且明显的吗?
  • foreach 循环不像 260 项那样有问题。 4 核。
  • Rene Vogt,TaskcreationOptions.LongRunning 只会让情况变得更糟,因为这表明创建的线程比必要的多。
  • 创建 260 个任务并不理想。如果你想要并行性,我会使用单个任务或Task.Run(() =&gt; Parallel.ForEach(...))
  • Charles Mager,是的,这似乎效果更好。

标签: c# concurrency task-parallel-library taskfactory


【解决方案1】:

您可以使用自定义TaskScheduler 将线程优先级设置为低值。 Windows 总是首先调度更高优先级的线程。

也许您需要为任务设置一个到期日期,这样它们就不会排队太多。听起来您需要低延迟处理。每个任务可以检查它的第一个动作,它是否在 N 秒之前被安排,如果是则立即退出。

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

【讨论】:

    【解决方案2】:

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

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

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

    如果您担心循环后代码变慢,请查看this answer from Jon Skeet。本质上,您应该使用 asyncawait 来完成此任务,或者在单独的任务上启动 Parallel.Foreach

    编辑:

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

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

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

    【讨论】:

    • Parallel.ForEach 块。这是不可接受的。即使你把它放在 Task.Run(() => Parallel.ForEach(...)) 中,只有在你有一组固定的压缩数据时才能解决问题。如果要解压的新数据一直到 - 一个接一个 - 你仍然需要为每个数据创建新任务怎么办?这种方法根本行不通。
    • 我不得不说 Task.Run(() => Parallel.ForEach(...)) 如果你只需要通过一次compressedChunkData,而不是每帧/迭代,它的效果会更好.
    • @JBeurer 您是否尝试过使用异步/等待。我将在答案中更新另一种方式
    猜你喜欢
    • 2011-11-19
    • 1970-01-01
    • 1970-01-01
    • 2022-06-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-09-20
    相关资源
    最近更新 更多