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

C# 管理许多重复的CPU密集型任务,并行运行?,c#,performance,task,delay,C#,Performance,Task,Delay,我需要尽可能快地不断执行20次重复的CPU密集型计算。因此,在中有20个任务包含循环方法: while(!token.IsCancellationRequested) 尽可能快地重复它们。所有计算均同时执行。不幸的是,这使程序没有响应,因此添加: await Task.Delay(15); 此时程序不会挂起,但增加延迟不是正确的方法,它不必要地降低了计算速度。它是没有MVVM的WPF程序。您建议采用什么方法使所有20项任务同时工作?每一个都将在完成后不断重复。我想保持CPU(所有核心)利

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

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

我认为您正在尝试做的事情与下面的简单示例类似,您在线程池上启动
CPUIntensiveMethod
,并等待其完成
Wait
Calculate
方法返回控制(允许UI线程继续工作),直到任务完成,此时它继续执行
while
循环

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

让我把整件事写下来作为回答。您混淆了两个相关但最终分离的概念(谢天谢地,这就是为什么您可以从这种区别中获益)。请注意,这些是我对这些概念的定义——你会听到相同事物有很多不同的名称,反之亦然

异步性是关于打破强加的操作同步性(即,op1等待op2,op3,op4…)。对我来说,这是一个更一般的概念,但现在它更常用于我所说的“内在异步性”——即算法本身是异步的,我们之所以使用同步编程,是因为我们必须这样做(多亏了
wait
async
,我们不再需要了,耶!)

这里的关键思想是等待。我无法在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
。您可能还想探索一些选项,如生产者-消费者队列等,以优化不同可能计算任务的实际运行时间。实际实现与您实际处理的内容紧密相关

或者,您希望在UI操作上启动任务,然后在结果准备就绪时启动它们的UI方法中使用结果值。在这种情况下,
wait
终于开始播放:

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
关键字在代码中根本没有位置


希望这一点现在更清楚,请随意提出更多问题。

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

定义一个线程数组,在每个逻辑处理器上运行一个线程。