任务 可以 用来表示在多个线程上进行的操作,但不一定 必须如此 。一个 可以
写一个永远只能执行单一线程的复杂TPL应用。例如,当您有一个任务代表某个数据的网络请求时,该任务将 不会
创建其他线程来实现该目标。这样的程序(希望)是异步的,但不一定是多线程的。
并行性在同一时间做很多事情。这可能是也可能不是多个线程的结果。
让我们在这里进行类比。
这是鲍勃做晚饭的方式:
- 他装满一锅水,煮沸。
- 然后,他将面食放入水中。
- 完成后,他将面食沥干。
- 他准备酱料。
- 他把所有调味料放在一个锅里。
- 他煮酱汁。
- 他把酱汁放在意大利面上。
- 他吃晚餐。
鲍勃在做饭时完全同步烹饪,没有多线程,异步或并行性。
这是简做饭的方法:
- 她装满一锅水,开始煮沸。
- 她准备酱料。
- 她把面食放在沸水中。
- 她把食材放在锅里。
- 她排干意大利面。
- 她把酱汁放在意大利面上。
- 她吃晚餐。
简在烹饪晚餐时利用异步烹饪(没有任何多线程)来实现并行性。
以下是Servy烹饪晚餐的方式:
- 他告诉鲍勃烧开一锅水,准备好后放入意大利面,然后将其送达意大利面。
- 他告诉简为酱汁准备原料,将其煮熟,然后在面食上食用。
- 他等待鲍勃和简完成。
- 他吃晚餐。
Servy利用了多个线程(工作人员),每个线程各自独立地同步完成工作,但彼此异步工作以实现并行性。
当然,如果我们考虑例如炉子有两个燃烧器还是只有一个燃烧器,这将变得更加有趣。如果我们的火炉有两个燃烧器,那么我们的两个线程,鲍勃和简,就能在不互相干扰的情况下完成工作。他们可能会碰到一点肩膀,或者每个人都时不时尝试从同一个柜子里拿东西,所以他们每个人都会放慢
一点
,但幅度不大。如果他们每个人都需要共享一个炉灶,那么无论何时对方工作时,他们实际上根本无法完成很多工作。在那种情况下,完成工作实际上不会比让一个人完全同步地做饭快得多,就像鲍勃独自一人做的那样。在这种情况下,我们正在使用多线程烹饪,
但是我们的烹饪并非平行 。 并非所有的多线程工作实际上都是并行工作
。这是在具有一个CPU的计算机上运行多个线程时发生的情况。实际上,您完成工作的速度不会比仅使用一个线程快,因为每个线程只是轮流进行工作。(这并不意味着多线程程序在一个内核CPU上是没有意义的,不是,而是,使用它们的原因并不是为了提高速度。)
我们甚至可以考虑使用任务并行库来考虑这些厨师的工作方式,以了解TPL的用途与以下每种类型的厨师相对应:
因此,首先我们有了bob,只需编写普通的非TPL代码并同步进行所有操作即可:
public class Bob : ICook{ public IMeal Cook() { Pasta pasta = PastaCookingOperations.MakePasta(); Sauce sauce = PastaCookingOperations.MakeSauce(); return PastaCookingOperations.Combine(pasta, sauce); }}然后,我们有了Jane,她启动了两个不同的异步操作,然后在启动每个异步操作之后等待它们中的两个以计算其结果。
public class Jane : ICook{ public IMeal Cook() { Task<Pasta> pastaTask = PastaCookingOperations.MakePastaAsync(); Task<Sauce> sauceTask = PastaCookingOperations.MakeSauceAsync(); return PastaCookingOperations.Combine(pastaTask.Result, sauceTask.Result); }}在此提醒一下,Jane正在使用TPL,并且她并行执行许多工作,但是她仅使用 一个线程 来完成工作。
然后是Servy,他
Task.Run用来创建一个任务,该任务 表示在另一个线程中进行工作
。他启动了两个不同的工人,让他们两个都同时做一些工作,然后等待两个工人完成。
public class Servy : ICook{ public IMeal Cook() { var bobsWork = Task.Run(() => PastaCookingOperations.MakePasta()); var janesWork = Task.Run(() => PastaCookingOperations.MakeSauce()); return PastaCookingOperations.Combine(bobsWork.Result, janesWork.Result); }}


