Per*_*tto 1 c# multithreading task winforms
我有一个复杂的数学问题要解决,我决定并行进行一些独立计算以缩短计算时间。在许多 CAE 程序中,如 ANSYS 或 SolidWorks,可以为此目的设置多个核心。
我创建了一个简单的 Windows 窗体示例来说明我的问题。这里的函数是在权力时代从课堂上CalculateStuff()
提出A
来的。对于 2 个任务是时间,对于 4 个任务是时间。Sample
1.2
max
max / 2
max / 4
我使用相同的代码计算了仅一个CalculateStuff()
函数或四个重复项(CalculateStuff1(), ...2(), ...3(), ...4()
每个任务一个)的结果操作时间。我不确定,是否对每个任务使用相同的功能很重要(无论如何,Math.Pow
是一样的)。我还尝试启用或禁用 ProgressBar。
该表表示所有 12 种情况的操作时间(秒)。我预计 2 和 4 个任务的速度会快 2 倍和 4 倍,但在某些情况下,4 个任务甚至比 1 个任务还要糟糕。我的计算机有 2 个处理器,每个有 10 个内核。根据调试窗口,CPU 使用率随着任务的增加而增加。我的代码有什么问题,或者我误解了什么?为什么多个任务不能提高操作时间?
private readonly ulong max = 400000000ul;
// Sample class
private class Sample
{
public double A { get; set; } = 1.0;
}
// Clear WinForm elements
private void Clear()
{
PBar1.Value = PBar2.Value = PBar3.Value = PBar4.Value = 0;
TextBox.Text = "";
}
// Button that launches 1 task
private async void BThr1_Click(object sender, EventArgs e)
{
Clear();
DateTime start = DateTime.Now;
Sample sample = new Sample();
await Task.Delay(100);
Task t = Task.Run(() => CalculateStuff(sample, PBar1, max));
await t;
TextBox.Text = (DateTime.Now - start).ToString(@"hh\:mm\:ss");
t.Dispose();
}
// Button that launches 2 tasks
private async void BThr2_Click(object sender, EventArgs e)
{
Clear();
DateTime start = DateTime.Now;
Sample sample1 = new Sample();
Sample sample2 = new Sample();
await Task.Delay(100);
Task t1 = Task.Run(() => CalculateStuff(sample1, PBar1, max / 2));
Task t2 = Task.Run(() => CalculateStuff(sample2, PBar2, max / 2));
await t1; await t2;
TextBox.Text = (DateTime.Now - start).ToString(@"hh\:mm\:ss");
t1.Dispose(); t2.Dispose();
}
// Button that launches 4 tasks
private async void BThr4_Click(object sender, EventArgs e)
{
Clear();
DateTime start = DateTime.Now;
Sample sample1 = new Sample();
Sample sample2 = new Sample();
Sample sample3 = new Sample();
Sample sample4 = new Sample();
await Task.Delay(100);
Task t1 = Task.Run(() => CalculateStuff(sample1, PBar1, max / 4));
Task t2 = Task.Run(() => CalculateStuff(sample2, PBar2, max / 4));
Task t3 = Task.Run(() => CalculateStuff(sample3, PBar3, max / 4));
Task t4 = Task.Run(() => CalculateStuff(sample4, PBar4, max / 4));
await t1; await t2; await t3; await t4;
TextBox.Text = (DateTime.Now - start).ToString(@"hh\:mm\:ss");
t1.Dispose(); t2.Dispose(); t3.Dispose(); t4.Dispose();
}
// Calculate some math stuff
private static void CalculateStuff(Sample s, ProgressBar pb, ulong max)
{
ulong c = max / (ulong)pb.Maximum;
for (ulong i = 1; i <= max; i++)
{
s.A = Math.Pow(s.A, 1.2);
if (i % c == 0)
pb.Invoke(new Action(() => pb.Value = (int)(i / c)));
}
}
Run Code Online (Sandbox Code Playgroud)
任务不是线程。“异步”并不意味着“同时”。
我的代码有什么问题,或者我误解了什么?
你误解了什么是任务。
您应该将任务视为可以按您想要的任何顺序执行的任务。以烹饪食谱为例:
如果这些不是任务而是同步代码,您将始终按照它们列出的确切顺序执行这些步骤。
如果它们是任务,那并不意味着这些工作将同时完成。你只是一个人(=一个线程),你一次只能做一件事。
您可以按您喜欢的任何顺序执行任务,您甚至可以暂停一项任务以开始另一项任务,但您仍然不能同时做多于一件事。无论您完成任务的顺序如何,完成所有三个任务所花费的总时间保持不变,并且这(本质上)并没有更快。
如果它们是线程,那就是雇佣 3 个厨师,这意味着这些工作可以同时完成。
异步性确实减少了等待的空闲时间。
请注意,异步代码可能会在同步代码空闲的情况下导致时间增加,例如等待网络响应。上面的例子中没有考虑到这一点,这正是我列出“cut [x]”作业而不是“wait for [x] to bool”的原因。
您的工作(计算)不是异步代码。它从不空闲(以一种可等待的方式),因此它同步运行。这意味着您不会从异步运行中获得任何好处。
将您的代码简化为一个更简单的示例:
private static void CalculateStuff(Sample s, ProgressBar pb, ulong max)
{
Thread.Sleep(5000);
}
Run Code Online (Sandbox Code Playgroud)
很简单地说,这项工作需要 5 秒,不能等待。如果您同时运行其中 3 个任务,它们仍将一个接一个处理,总共需要15 秒。
如果您的任务中的工作实际上是可等待的,您会看到时间上的好处。例如:
private static async void CalculateStuff(Sample s, ProgressBar pb, ulong max)
{
await Task.Delay(5000);
}
Run Code Online (Sandbox Code Playgroud)
这项工作需要 5 秒,但可以等待。如果您同时运行其中 3 个任务,您的线程将不会浪费时间空闲(即等待延迟),而是会开始执行以下任务。由于它可以同时等待(即什么都不做)这些任务,这意味着总处理时间总共需要5 秒(加上一些可以忽略不计的开销成本)。
根据调试窗口,CPU 使用率随着任务的增加而增加。
任务管理需要很小的开销成本,这意味着与同步代码相比,工作总量(可以用 CPU 使用率随着时间的推移来衡量)略高。这是可以预料的。
与从编写良好的异步代码中获得的好处相比,这种小成本通常相形见绌。但是,您的代码根本没有利用异步性带来的实际好处,因此您只看到了开销成本,而没有看到它的好处,这就是为什么您的监控会为您提供与预期相反的结果。
- 我的电脑有 2 个处理器,每个处理器 10 个内核。
CPU 内核、线程和任务是三种截然不同的野兽。
任务由线程处理,但它们不一定具有一对一的映射关系。以 4 个开发人员的团队为例,该团队有 10 个需要解决的错误。虽然这意味着不可能同时解决所有 10 个错误,但这些开发人员(线程)可以一个接一个接下工单(任务),每当他们完成上一个工单时,就接下一个新工单(任务)(任务)。
CPU 内核就像工作站。工作站(CPU 内核)比开发人员(线程)少是没有意义的,因为您最终会遇到闲置的开发人员。
此外,您可能不希望您的开发人员能够声明所有工作站。也许 HR 和会计(= 其他操作系统进程)也需要有一些有保障的工作站,以便他们可以完成他们的工作。
公司(= 计算机)不会因为开发人员正在修复一些错误而陷入停顿。这是过去在单核机器上发生的情况——如果一个进程声明了 CPU,其他事情就不会发生。如果该进程需要很长时间或挂起,则一切都会冻结。
这就是为什么我们有一个线程池。这里没有直接的现实世界类比(除非可能是咨询公司动态调整向您的公司发送多少开发人员),但线程池基本上能够决定允许在该公司工作的开发人员数量同时为了确保开发任务能尽快被看到,同时也确保其他部门仍然可以在工作站上做他们的工作。
这是一个谨慎的平衡行为,不要派太多的开发人员,因为这会淹没系统,同时也不要派的开发人员太少,因为这意味着工作完成得太慢。
您的线程池的确切配置不是我可以通过简单的问答来解决的问题。但是,与您拥有的任务数量相比,您描述的行为与 CPU(专用于您的运行时)和/或线程一致。
归档时间: |
|
查看次数: |
855 次 |
最近记录: |