管理许多重复的CPU密集型任务,并行运行?

时间:2014-02-20 09:59:24

标签: c# performance task delay

我需要尽可能快地执行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);
}

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

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

3 个答案:

答案 0 :(得分:5)

让我把整个事情写成答案。你混淆了两个相关的,但最终是分开的概念(幸运的是 - 这就是为什么你可以从这个区别中受益)。请注意,这些是我对这些概念的定义 - 你会听到大量不同的名字,反之亦然。反之亦然。

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

这里的关键是等待。我无法在CPU上做任何事情,因为我在等待I / O操作的结果。这种异步编程基于异步操作几乎不需要CPU的思想 - 它们是I / O绑定的,而不是CPU绑定的。

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

使用异步(等待),使用更多线程将提高表观速度,而不管可用逻辑核心的数量。这是因为99%的时间,代码实际上没有做任何工作,只是等待。

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

线条模糊批次。这是因为您可能甚至不知道正在发生的事情,例如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关键字实际上在您的代码中根本没有位置。

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

答案 1 :(得分:1)

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

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

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

  1. 您的计算需要许多频繁的UI更新:将他们所需的更新信息放入队列并在Timer事件中更新它们。
  2. 您的计算需要稀缺的UI更新:使用Control.BeginInvoke
  3. 等调用方法更新UI

答案 2 :(得分:0)

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

认为你想要做的事情就像下面的简单例子,你在线程池上启动CPUIntensiveMethod并等待它完成。 awaitCalculate方法返回控制(允许UI线程继续工作),直到任务完成,此时它继续while循环。

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