我需要尽可能快地执行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";
}));
答案 0 :(得分:5)
让我把整个事情写成答案。你混淆了两个相关的,但最终是分开的概念(幸运的是 - 这就是为什么你可以从这个区别中受益)。请注意,这些是我对这些概念的定义 - 你会听到大量不同的名字,反之亦然。反之亦然。
异步性是关于打破强加的操作同步性(即op 1等待op 2,等待op 3,等待op 4 ......)。对我来说,这是更一般的概念,但现在它更常用于表示我称之为“固有的异步性” - 即。算法本身是异步的,我们只使用同步编程,因为我们必须(并且感谢await
和async
,我们不再需要了,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澄清的那样,您的提案中的async
和await
部分不会对您的问题有所帮助,Task.Run
不适合您的工作;使用线程是一种更好的方法。
定义一个Threads数组,在每个逻辑处理器上运行一个。在他们之间分配您的任务数据,并通过BufferBlock
中提供的TPL DataFlow library
控制您的20个重复计算。
为了保持UI响应,我建议采用两种方法:
Control.BeginInvoke
答案 2 :(得分:0)
正如@Luaan所说,我强烈建议您阅读async
/ await
,关键是它没有引入任何并行性。
我认为你想要做的事情就像下面的简单例子,你在线程池上启动CPUIntensiveMethod
并等待它完成。 await
从Calculate
方法返回控制(允许UI线程继续工作),直到任务完成,此时它继续while
循环。
private async Task Calculate()
{
while (!CTSFor_task001.IsCancellationRequested)
{
await Task.Run(CPUIntensiveMethod);
}
}