如何使用c#并行运行两个任务?



假设我有这段代码应该并行运行两个任务,我对c#中的并行任务没有很好的了解,我想从这段代码开始理解这个概念,我想做的是同时运行两个任务(异步)

public async Task Main() {
var task1 = Task.Run(() => DoWork());
var task2 = Task.Run(() => CleanIt());
await Task.WhenAll(task1, task2);
}
private void CleanIt() {
int sum = 0;
for (int i = 0; i < 10; i++) {
Console.WriteLine(" Thread two " + i);
}
}
private void DoWork() {
int sum = 0;
for (int i = 0; i < 10; i++) {
Console.WriteLine(" Thread one " + i);
}
}

我得到的结果:

Thread one 0
Thread two 0
Thread one 1
Thread one 2
Thread one 3
Thread one 4
Thread one 5
Thread one 6
Thread one 7
Thread one 8
Thread one 9
Thread two 1
Thread two 2
Thread two 3
Thread two 4
Thread two 5
Thread two 6
Thread two 7
Thread two 8
Thread two 9

我想这样显示结果:

Thread one 0
Thread two 0
Thread one 1
Thread two 1
Thread one 2
Thread two 2
....

我怎样才能达到这个结果?

异步代码的思想是告诉处理器不要等待此任务完成,而是同时启动其他任务。自"其他"起;任务也不会等待原始任务完成,如果不编写同步代码,就无法确保任务保持同步。当你启动多个异步任务时,你放弃了对这些任务执行时间的控制给了操作系统及其处理器调度算法,并承诺它们最终会完成。

调度算法将选择最有效的方式来执行你的代码,考虑到每一个需要CPU时间的其他程序,并决定如何运行你的任务。它可以选择先运行一个任务完成,然后再运行另一个任务。或者,它可以选择在不同的处理器核心上运行它们,或者在同一核心上在它们之间切换。这不是由您决定的,而是由操作系统决定的。

具体来说,在c#中,异步任务是使用线程池运行的,所以你甚至不能选择是否运行多个线程。如果任务使用同一个线程运行,它们不能在多个内核上运行,这意味着它们不能并行运行。如果你想让任务在多个核心上运行,你需要使用Thread的显式性,但无论如何,你不能控制每个线程何时运行或在哪个核心上运行。

如果您有长时间运行的任务,您可能会看到您的输出从一种切换到另一种。每个任务运行的确切时间取决于许多您无法控制的因素,并且每次运行程序时可能会有所不同。

如果你需要你的代码同步运行(即在运行一个任务之前等待其他任务继续),你需要编写同步代码,而不是异步代码。

按顺序做两件事

(1)启动任务1。(2)启动任务2。

现在启动任务,即执行Task.Run(),是缓慢且昂贵的。这大概需要5毫秒。运行这个非常短的任务可能只需要1ms。所以任务1在任务2开始之前就完成了。

<>之前时间-> 1 ms…1 1 ms…女士女士…1 ms…1毫秒 .......... 1毫秒 ............ 1 1 ms…女士女士女士1女士……主要设置任务1 ..................................][设置任务2 ............................................][主要]结束task1 [DoWork()开始][DoWork()结束]task2 [DoWorkToo() starts] [DoWorkToo() ends]如果你想让任务并行运行,你必须使它们的运行时间比任务启动时间长。一种解决方案是简单地将循环运行数千次,但这可能很笨拙。更好的方法是让任务在循环执行期间休眠,这样当任务2启动时,task1仍在运行。

下面是一个运行时间较长的任务的例子,很好地显示了执行是如何交错的:

using System;
using System.Threading;
using System.Threading.Tasks;
using System.Diagnostics; // stopwatch
namespace TwoTasks2
{
class Program
{
static Stopwatch globalStopWatch = new Stopwatch();
private static void DoWork()
{
Console.WriteLine("========================= Entering DoWork() after " + globalStopWatch.ElapsedMilliseconds);
int sum = 0;
Stopwatch sw = new Stopwatch();
sw.Start();
for (int i = 0; i < 100000; i++)
{
if (i % 10000 == 0) 
{ 
Console.WriteLine("Task one " + 10000 + " cycles took " + sw.ElapsedMilliseconds + " ms");
sw.Stop();
sw.Start();
Console.WriteLine("Thread ID: " + Thread.CurrentThread.ManagedThreadId);
Console.WriteLine("CPU ID:    " + Thread.GetCurrentProcessorId());
}
}
}
private static void DoWorkToo()
{
Console.WriteLine("========================= Entering DoWorkToo() after " + globalStopWatch.ElapsedMilliseconds);
int sum = 0;
Stopwatch sw = new Stopwatch();
sw.Start();
for (int i = 0; i < 100000; i++)
{
if (i % 10000 == 0) 
{
Console.WriteLine("                                  Task two " + 10000 + " cycles took " + sw.ElapsedMilliseconds + " ms");
sw.Stop();
sw.Start();
Console.WriteLine("                                  Thread ID: " + Thread.CurrentThread.ManagedThreadId);
Console.WriteLine("                                  CPU ID:    " + Thread.GetCurrentProcessorId());

}
}
}
public static void Main()
{
globalStopWatch.Start();
var task1 = Task.Run(() => DoWork());
long ms = globalStopWatch.ElapsedMilliseconds;
Console.WriteLine("--------------------- RunTask 1 took " + ms);
var task2 = Task.Run(() => DoWorkToo());
Console.WriteLine("--------------------- RunTask 2 took " + (globalStopWatch.ElapsedMilliseconds-ms));
var tasks = new Task[] { task1, task2 };
Task.WaitAll(tasks);
}
}
}

在我的机器上的输出示例,调试构建:

--------------------- RunTask 1 took 23
========================= Entering DoWork() after 39
--------------------- RunTask 2 took 18
Task one 10000 cycles took 0 ms
Thread ID: 4
========================= Entering DoWorkToo() after 41
Task two 10000 cycles took 0 ms
Thread ID: 5
CPU ID:    1
CPU ID:    2
Task two 10000 cycles took 1 ms
Thread ID: 5
Task one 10000 cycles took 2 ms
Thread ID: 4
CPU ID:    4
CPU ID:    1
Task one 10000 cycles took 2 ms
Thread ID: 4
CPU ID:    4
Task two 10000 cycles took 2 ms
Thread ID: 5
Task one 10000 cycles took 2 ms
Thread ID: 4
CPU ID:    4
CPU ID:    1
Task one 10000 cycles took 2 ms
Thread ID: 4
CPU ID:    4
Task two 10000 cycles took 2 ms
Thread ID: 5
CPU ID:    1
Task one 10000 cycles took 2 ms
Thread ID: 4
CPU ID:    2
Task two 10000 cycles took 2 ms
Thread ID: 5
CPU ID:    1
Task one 10000 cycles took 3 ms
Thread ID: 4
CPU ID:    2
Task two 10000 cycles took 2 ms
Thread ID: 5
CPU ID:    1
Task one 10000 cycles took 3 ms
Thread ID: 4
CPU ID:    2
Task two 10000 cycles took 2 ms
Thread ID: 5
CPU ID:    1
Task one 10000 cycles took 3 ms
Thread ID: 4
CPU ID:    2
Task two 10000 cycles took 2 ms
Thread ID: 5
CPU ID:    3
Task one 10000 cycles took 3 ms
Thread ID: 4
CPU ID:    4
Task two 10000 cycles took 2 ms
Thread ID: 5
CPU ID:    10
Task two 10000 cycles took 3 ms
Thread ID: 5
CPU ID:    1

发布版本更加有序:

--------------------- RunTask 1 took 21
========================= Entering DoWork() after 37
--------------------- RunTask 2 took 16
Task one 10000 cycles took 0 ms
Thread ID: 4
CPU ID:    4
Task one 10000 cycles took 0 ms
Thread ID: 4
CPU ID:    6
Task one 10000 cycles took 1 ms
Thread ID: 4
CPU ID:    2
Task one 10000 cycles took 1 ms
Thread ID: 4
CPU ID:    2
Task one 10000 cycles took 1 ms
Thread ID: 4
CPU ID:    3
Task one 10000 cycles took 1 ms
Thread ID: 4
CPU ID:    6
Task one 10000 cycles took 2 ms
Thread ID: 4
CPU ID:    2
========================= Entering DoWorkToo() after 39
Task two 10000 cycles took 0 ms
Thread ID: 5
CPU ID:    11
Task one 10000 cycles took 2 ms
Thread ID: 4
CPU ID:    10
Task two 10000 cycles took 0 ms
Thread ID: 5
CPU ID:    11
Task one 10000 cycles took 2 ms
Thread ID: 4
CPU ID:    10
Task two 10000 cycles took 0 ms
Thread ID: 5
CPU ID:    11
Task one 10000 cycles took 2 ms
Thread ID: 4
CPU ID:    10
Task two 10000 cycles took 0 ms
Thread ID: 5
CPU ID:    1
Task two 10000 cycles took 0 ms
Thread ID: 5
CPU ID:    1
Task two 10000 cycles took 0 ms
Thread ID: 5
CPU ID:    11
Task two 10000 cycles took 0 ms
Thread ID: 5
CPU ID:    11
Task two 10000 cycles took 0 ms
Thread ID: 5
CPU ID:    11
Task two 10000 cycles took 0 ms
Thread ID: 5
CPU ID:    1
Task two 10000 cycles took 1 ms
Thread ID: 5
CPU ID:    1

在第一个任务开始运行之前,它花费了惊人的35ms !这在现代cpu上是永恒的。然后第二个任务开始得更快。

任务甚至在打印行到控制台之间轮流执行。您还可以看到,即使是同一个线程也可以在Windows认为合适的情况下从一个内核跳到另一个内核。(这让我很吃惊。我的Ryzen有6个真正的内核,没有任何明显的负载,所以我让任务在它们所在的地方运行。

最新更新