使用Tasks (TPL)库是否使应用程序成为多线程的?
本文关键字:应用程序 多线程 是否 Tasks TPL 使用 | 更新日期: 2023-09-27 18:05:03
最近在一次面试中,我被问到这个问题。
问:你写过多线程应用程序吗?:是的
问:介意解释更多吗?
A:我使用Tasks
(Task Parallel library)来执行waiting for some info from internet while loading UI
等任务。这提高了应用程序的可用性。
问:但是,只要你用了TPL
就意味着你写了一个multithreaded
应用程序?
我:(不知道该说什么)
那么,到底什么是多线程应用程序呢?它与使用Tasks
有什么不同吗?
任务可以用来表示在多个线程中发生的操作,但是它们不有这样做。一个可以编写只在单个线程中执行的复杂TPL应用程序。例如,当您有一个任务表示对某些数据的网络请求时,该任务是而不是将创建额外的线程来完成该目标。这样的程序(希望)是异步的,但不一定是多读的。
并行是指同时做多件事。这可能是也可能不是多个线程的结果。
让我们打个比方。
Bob是怎么做晚饭的:
- 他装满一壶水,然后把它煮沸。
- 然后他把意大利面放进水里。
- 意大利面煮好后,他把水沥干。
- 他在准备他的酱料。
- 他把做酱汁的所有原料都放在平底锅里。
- 他在煮他的酱。
- 他把酱汁放在意大利面上。
- 他吃晚饭了。
Bob在烹饪晚餐时完全是同步的,没有多线程、异步或并行。
Jane是这样做晚餐的:
- 她装满一壶水,开始煮。
- 她在准备她的酱料。 她把意大利面放进沸水里。
- 她把食材放进平底锅。
- 她喝光了她的面食。 她把酱汁放在意大利面上。
- 她吃她的晚餐。
Jane利用异步烹饪(没有任何多线程)在烹饪晚餐时实现并行。
Servy是这样做晚餐的:
- 他告诉Bob烧一壶水,准备好了就把意大利面放进去,然后端上意大利面。
- 他告诉Jane准备做酱汁的原料,煮熟,然后煮好后放在意大利面上。
- 他等待Bob和Jane完成。
- 他吃晚饭了。
Servy利用了多个线程(worker),每个线程都同步地完成自己的工作,但它们彼此异步地工作,以实现并行性。
当然,如果我们考虑,例如,我们的炉子是有两个燃烧器还是只有一个燃烧器,这就变得更有趣了。如果我们的炉子有两个燃烧器,那么我们的两条线,鲍勃和简,都能在不互相妨碍的情况下完成自己的工作。他们可能会撞一下肩膀,或者每个人时不时地试图从同一个柜子里抓东西,所以他们每个人都会减慢一个位,但不会太多。如果他们每个人都需要共用一个炉子,那么当另一个人在工作时,他们实际上根本无法完成很多工作。在这种情况下,工作实际上不会比让一个人完全同步烹饪更快,就像鲍勃独自一人时所做的那样。在这种情况下,我们正在处理多个线程,,但我们的处理不是并行的。并非所有多线程工作实际上都是并行工作。当您在一台具有一个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);
}
}
Task
是对未来工作完成的承诺。当使用它时,您可以将其用于I/O based
工作,其中不要求使用多个线程来执行代码。一个很好的例子是使用c# 5的async/await
和HttpClient
的特性来完成基于网络的I/O工作。
但是,您可以利用TPL
来执行多线程工作。例如,当使用Task.Run
或Task.Factory.Startnew
启动一个新任务时,幕后的工作在ThreadPool
上为你排队,TPL
为我们抽象出来,允许你使用多个线程。
使用多线程的常见场景是当您有CPU限制的工作可以同时(并行)完成时。使用多线程应用程序会带来很大的责任。
所以我们看到使用TPL
并不一定意味着使用多个线程,但你绝对可以利用它来做多线程。
问:但是,只要你使用过TPL就意味着你写了一个多线程应用程序?
聪明的问题,任务!=多线程,使用TaskCompletionSource你可以创建一个Task
,它可以在单线程中执行(可能是UI线程)。
Task
只是对一个可能在未来完成的操作的抽象。这并不意味着代码是多线程的。通常Task
涉及多线程,但不一定总是这样。
记住,只知道TPL
,你不能说你知道多线程。有很多概念你需要涵盖。
- 线程
- 同步原语
- 线程安全
- 异步编程 并行编程是TPL的一部分。
- 和更多…
当然还有Task并行库。
注意:这不是完整的列表,这些只是我的想法。
人力资源><学习资源:
- Eric的旧博客
- Eric的新博客
- Stephen toub的博客
对于单独线程,我建议http://www.albahari.com/threading/
对于视频教程,我建议使用Pluralsight。这是值得的,但值得的。最后但并非最不重要:是的,当然,它是Stackoverflow。
我的5美分:您不必显式地使用Task.Run
或Task.Factory.StartNew
来使您的TPL应用程序多线程。想想看:
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
之后的代码在不同线程上并发执行。以类似的方式,并发可以通过异步套接字API, HttpClient
, Stream.ReadAsync
或任何其他使用线程池(包括IOCP池线程)引入。
ThreadPool
。即使是一个简单的控制台应用程序也会显示多个System.Diagnostics.Process.GetCurrentProcess().Threads.Count
线程。面试官应该问你是否写过并发(或并行)代码。