【问题标题】:Manage many repetitive, CPU intensive tasks, running parallelly?管理许多重复的、CPU 密集型任务,并行运行?
【发布时间】:2014-02-20 09:59:24
【问题描述】:

我需要尽可能快地不断执行 20 次重复的 CPU 密集型计算。所以有 20 个任务包含循环方法:

while(!token.IsCancellationRequested) 

尽可能快地重复它们。所有计算同时进行。不幸的是,这使程序没有响应,所以补充说:

await Task.Delay(15); 

此时程序没有挂起,但添加延迟不是正确的方法,它不必要地减慢了计算速度。它是没有 MVVM 的 WPF 程序。你建议用什么方法让所有 20 个任务同时工作?它们中的每一个都会在完成后不断重复。我想将 CPU(所有内核)利用率保持在最大值(或接近)以确保最佳效率。

编辑: 有 20 个控件,用户可以在其中调整一些参数。计算在:

private async Task Calculate()
{
   Task task001 = null;
   task001 = Task.Run(async () => 
   {
      while (!CTSFor_task001.IsCancellationRequested)
      {
          await Task.Delay(15);
          await CPUIntensiveMethod();
      }
   }, CTSFor_task001.Token);
}

每个控件都是独立的。计算 100% 受 CPU 限制,没有 I/O 活动。 (所有值都来自变量)在计算过程中一些UI项目的值发生了变化:

 this.Dispatcher.BeginInvoke(new Action(() =>
     {
          this.lbl_001.Content = "someString";
     }));

【问题讨论】:

  • 你能添加你目前使用的代码吗?因为 C#/WPF 中的多线程可以通过多种方式完成,而且您已经在谈论两种不同的机制(任务/并行)。
  • 还有,有什么样的作品?工作项是否相关?它们真的 100% 受 CPU 限制,还是有 I/O 活动导致延迟?什么“挂”?用户界面?还是只有一些任务获得“公平”的 CPU 份额?你想要异步还是并行? Task 用于异步性,not 并行性,即。它旨在让您可以“同时”有效地运行许多不受 CPU 限制的操作,而无需额外线程的额外成本。
  • @BatteryBackupUnit 请不要手动更改线程优先级。只是不要。它不会做你认为它会做的事情。有关更多信息,请参阅codinghorror.com/blog/2006/08/thread-priorities-are-evil.html :) 另外,我希望 OP 实际上不会与 UI 并行运行工作:)
  • 好的,所以你应该阅读一下await+async。你显然认为他们做了他们没有做的事情。它们设计用于阻塞而不导致 CPU 使用的操作(通常等待某种 I/O)。它们不是神奇的“现在发生并行”关键字。您的方案根本不保证使用await/async。事实上,您的代码中发生的唯一正确异步的事情是await Task.Delay(15) 位:) 您的代码还有一些问题,但这是问题的症结所在。 blog.stephencleary.com/2013/11/…
  • @as74:我相信你会从使用 TPL 数据流库中受益。

标签: c# performance task delay


【解决方案1】:

让我把整个事情写下来作为答案。您混淆了两个相关但最终独立的概念(谢天谢地 - 这就是您可以从区别中受益的原因)。请注意,这些是我对这些概念的定义 - 您会听到大量相同事物的不同名称,反之亦然。

异步性是关于打破操作的强加同步性(即 op 1 等待 op 2,它等待 op 3,它等待 op 4...)。对我来说,这是一个更普遍的概念,但现在它更常用来表示我所说的“固有的异步性”——即。算法本身是异步的,我们只使用同步编程,因为我们必须这样做(感谢awaitasync,我们不必再这样做了,耶!)。

这里的关键思想是等待。我不能在 CPU 上做任何事情,因为我在等待 I/O 操作的结果。这种异步编程是基于异步操作几乎不占用 CPU 的思想——它们受 I/O 限制,而不是 CPU 限制。

并行性是一种特殊的一般异步性,其中操作主要不是相互等待。换句话说,我不是在等待,而是在工作。如果我有四个 CPU 内核,理想情况下我可以使用四个计算线程来进行这种处理 - 在理想情况下,我的算法将随着可用内核的数量线性扩展。

通过异步(等待),无论可用逻辑内核的数量如何,使用更多线程都会提高明显速度。这是因为在 99% 的情况下,代码实际上并没有做任何工作,它只是在等待。

使用并行性(working),使用更多线程与可用工作核心的数量直接相关。

线条模糊了很多。那是因为你甚至可能不知道正在发生的事情,例如 CPU(和整个计算机)本身就是令人难以置信的异步 - 它显示的明显同步性只是为了让你同步编写代码;所有优化和异步性都受到这样一个事实的限制,即在输出时,一切都再次同步。如果每次执行i ++ 时 CPU 都必须等待内存中的数据,那么您的 CPU 运行在 3 GHz 还是 100 MHz 都没有关系。您出色的 3 GHz CPU 将有 99% 的时间处于空闲状态。

话虽如此,您的计算任务受 CPU 限制。它们应该使用并行执行,因为它们正在工作。另一方面,UI 是 I/O 绑定的,它应该使用异步代码。

实际上,您的async Calculate 方法所做的只是掩盖了它不是实际上天生异步的事实。相反,您希望运行它与 I/O 异步。

换句话说,不是Calculate 方法是异步的。 UI 希望它与自身异步运行。从那里删除所有Task.Run 混乱,它不属于。

接下来要做什么?这取决于您的用例。基本上有两种情况:

您希望任务从头到尾始终在后台运行。在这种情况下,只需为它们中的每一个创建一个线程,并且根本不要使用Task。您可能还想探索一些选项,如生产者-消费者队列等,以优化不同可能计算任务的实际运行时间。实际实现与您实际处理的内容紧密相关。

或者,您希望在 UI 操作上启动任务,然后在结果准备好时在启动它们的 UI 方法中处理结果值。。既然如此,await 终于上场了:

private btn_Click(object sender, EventArgs e)
{
  var result = await Task.Run(Calculate);

  // Do some (little) work with the result once we get it
  tbxResult.Text = result;
}

async 关键字实际上在您的代码中根本没有位置。

希望现在更清楚,请随时提出更多问题。

【讨论】:

    【解决方案2】:

    因此,您真正寻求的是阐明一种在保持 UI 响应式的同时最大限度提高性能的良好做法。正如 Luaan 所澄清的,您提案中的 asyncawait 部分不会对您的问题有益,而 Task.Run 不适合您的工作;使用线程是更好的方法。

    定义一个线程数组以在每个逻辑处理器上运行一个。通过TPL DataFlow library中提供的BufferBlock在它们之间分配您的任务数据并控制您的20次重复计算。

    为了保持 UI 响应,我建议两种方法:

    1. 您的计算需要许多频繁的 UI 更新:将所需的更新信息放入队列中并在 Timer 事件中更新。
    2. 您的计算需要很少的 UI 更新:使用 Control.BeginInvoke 之类的调用方法更新 UI

    【讨论】:

      【解决方案3】:

      正如@Luaan 所说,我强烈建议阅读async/await,关键是它不会引入任何并行性。

      认为您正在尝试做的是类似于下面的简单示例,您在线程池中启动CPUIntensiveMethod 并等待其完成。 awaitCalculate 方法返回控制(允许 UI 线程继续工作),直到任务完成,此时它继续 while 循环。

      private async Task Calculate()
      {
         while (!CTSFor_task001.IsCancellationRequested)
         {
             await Task.Run(CPUIntensiveMethod);
         }   
      }
      

      【讨论】:

        猜你喜欢
        • 2020-06-16
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-08-06
        • 1970-01-01
        • 2020-10-11
        • 1970-01-01
        • 2021-08-15
        相关资源
        最近更新 更多