是否使用任务(TPL)库使应用程序multithreading?

最近在接受采访时,我得到了这个问题。

问:你写了multithreading应用程序吗?

答:是的

问:要多解释一下?

答:我使用Tasks (任务并行库)来执行一些任务,如waiting for some info from internet while loading UI 。 这提高了我的应用程序可用性。

问:但是,您刚刚使用过TPL意味着您已经编写了multithreaded应用程序?

我:(不知道该说什么1)

那么,什么是一个multithreading应用程序? 与使用Tasks不同吗?

任务可以用来表示在多个线程上进行的操作,但是他们不需要。 可以编写复杂的TPL应用程序,这些应用程序只能在单个线程中执行。 当你有一个任务,例如,代表一些数据的networking请求,这个任务不会创build额外的线程来实现这个目标。 这样的程序(希望)是asynchronous的,但不一定是multithreading的。

并行性在同一时间做了不止一件事情。 这可能是也可能不是多个线程的结果。

让我们在这里进行比喻。


下面是鲍勃如何烹饪晚餐:

  1. 他把一壶水烧开了。
  2. 然后他把意大利面放在水里。
  3. 当它完成时,他把意大利面排干了。
  4. 他准备他的酱料。
  5. 他把所有的酱料都放在平底锅里。
  6. 他煮他的酱汁。
  7. 他把他的酱放在他的意大利面上。
  8. 他吃晚饭。

鲍勃煮完晚餐时,完全同步烹饪,没有multithreading,asynchronous或并行性。


简是这样做的晚餐:

  1. 她装满了一壶水,然后开始煮。
  2. 她准备调味料。
  3. 她把面食放在沸水里。
  4. 她把配料放在平底锅里
  5. 她把意大利面倒掉了
  6. 她把酱放在她的意大利面上。
  7. 她吃晚餐。

Jane在烹调晚餐时利用asynchronous烹饪(没有任何multithreading)来实现并行性。


下面是Servy如何做饭的晚餐:

  1. 他告诉鲍勃烧开一壶水,准备好时放进面食,然后服务面食。
  2. 他告诉简,准备调味酱的原料,煮熟,然后在做完意大利面后供应。
  3. 他等着Bob和Jane完成。
  4. 他吃他的晚餐。

Servy利用多个线程(工作人员),每个线程(工作人员)各自同步地工作,但彼此asynchronous工作以实现并行性。

当然,如果我们考虑,例如,我们的炉子是两个燃烧器还是只有一个,这就更加有趣了。 如果我们的炉子有两个燃烧器,那么我们的两个线程,Bob和Jane,都能够做到他们的工作,而不是彼此相处。 他们可能会碰一下肩膀,或者每次都试图从同一个橱柜里拿东西,所以他们每个人都会放慢一点 ,但是不会太多。 如果他们每个人都需要分享一个炉子的燃烧器,那么只要对方正在工作,他们实际上也不会做太多的工作。 在这种情况下,工作实际上不会比只让一个人完全同步做饭更快,就像鲍勃自己做饭一样。 在这种情况下,我们用multithreading烹饪但是我们的烹饪不是并行的并非所有的multithreading工作实际上是并行工作 。 在一台CPU上运行多个线程时会发生这种情况。 你实际上并没有比使用一个线程更快地完成工作,因为每个线程只是轮stream工作。 (这并不意味着multithreading程序在一个内核CPU上毫无意义,它们不是,只是使用它们的原因不是为了提高速度。)


我们甚至可以考虑这些厨师如何使用任务并行库来完成他们的工作,以查看TPL对应于这些types的厨师的用途:

所以首先我们有bob,只需编写普通的非TPL代码,

 public class Bob : ICook { public IMeal Cook() { Pasta pasta = PastaCookingOperations.MakePasta(); Sauce sauce = PastaCookingOperations.MakeSauce(); return PastaCookingOperations.Combine(pasta, sauce); } } 

然后,我们让Jane开始了两个不同的asynchronous操作,然后在启动每个asynchronous操作之后等待他们两个来计算她的结果。

 public class Jane : ICook { public IMeal Cook() { Task<Pasta> pastaTask = PastaCookingOperations.MakePastaAsync(); Task<Sauce> sauceTask = PastaCookingOperations.MakeSauceAsync(); return PastaCookingOperations.Combine(pastaTask.Result, sauceTask.Result); } } 

在这里提醒一下,简正在使用TPL,她同时在做大量的工作,但她只用一个线程来完成工作。

然后我们有Servy,他使用Task.Run来创build一个代表在另一个线程中工作的任务。 他启动了两个不同的工作人员,他们各自同时做一些工作,然后等待两个工人完成。

 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); } } 

Task是未来工作的承诺。 使用它时,可以将其用于I/O based工作, 而不需要使用多个线程来执行代码。 一个很好的例子就是使用C#5的async/awaitfunction来处理基于networking的I / O工作的HttpClient

但是,您可以利用TPL来执行multithreading工作。 例如,当使用Task.RunTask.Factory.Startnew来启动一个新的任务时,幕后的工作就会在ThreadPool上排队等候, TPL为我们提供了抽象,允许你使用多个线程。

使用multithreading的一个常见情况是当你有CPU绑定的工作,可以同时完成(并行)。 使用multithreading应用程序有很大的责任。

所以我们看到和TPL一起工作并不意味着使用multithreading,但是你一定可以利用它来做multithreading。

问:但是,您刚刚使用过TPL意味着您已经编写了multithreading应用程序?

聪明的问题,任务!=multithreading,使用TaskCompletionSource你可以创build一个Task ,可以在单线程(可能只是UI线程)本身执行。

Task只是一个对将来可能完成的操作的抽象。 这并不意味着代码是multithreading的。 通常Task涉及multithreading,不一定总是。

只记得TPL知识你不能说你知道multithreading。 你需要覆盖很多概念。

  • 线
  • 同步原语
  • 线程安全
  • asynchronous编程
  • 并行编程是TPL的一部分。
  • 还有很多…

当然还有Task平行库。

注意:这不是完整的列表,这些只是从我的头顶。


要学习的资源:

  • 埃里克的旧博客
  • 埃里克的新博客
  • Stephen toub的博客

对于单独的线程,我build议http://www.albahari.com/threading/

对于video教程,我build议Pluralsight 。 这是付出,但值得的成本。

最后但并非最不重要的:是的,当然是Stackoverflow 。

我的5分钱:你不必与Task.RunTask.Factory.StartNew明确地进行线程Task.Factory.StartNew ,使你的TPL应用程序成为multithreading的。 考虑这个:

 async static Task TestAsync() { Func<Task> doAsync = async () => { await Task.Delay(1).ConfigureAwait(false); Console.WriteLine(new { Thread.CurrentThread.ManagedThreadId }); }; var tasks = Enumerable.Range(0, 10).Select(i => doAsync()); await Task.WhenAll(tasks); } // ... TestAsync().Wait(); 

doAsync内部await后的代码在不同的线程上同时执行。 以类似的方式,可以使用asynchronous套接字API, HttpClientStream.ReadAsync或使用线程池(包括IOCP池线程)的其他任何东西引入并发。

形象地说,每个.NET应用程序都是multithreading的,因为Framework广泛地使用了ThreadPool 。 即使一个简单的控制台应用程序也会为System.Diagnostics.Process.GetCurrentProcess().Threads.Count显示多个线程。 你的面试官应该问你是否编写了并行 (或并行)代码。