简体   繁体   English

使用任务(TPL)库是否会使应用程序成为多线程?

[英]Does using Tasks (TPL) library make an application multithreaded?

Recently when being interviewed, I got this question. 最近在接受采访时,我遇到了这个问题。

Q: Have you written multithreaded applications? 问:您是否编写了多线程应用程序?

A: Yes 答:可以

Q: Care to explain more? 问:想解释更多吗?

A: I used Tasks (Task Parallel library) to carry out some tasks like waiting for some info from internet while loading UI . 答:我使用Tasks (任务并行库)执行一些任务,例如waiting for some info from internet while loading UI This improves my application usability. 这提高了我的应用程序的可用性。

Q: But, just you have used TPL means that you have written an multithreaded application? 问:但是,仅使用TPL意味着您编写了multithreaded应用程序?

Me: (Not sure what to say1) 我:(不知道怎么说)

So, whats exactly a multi-threaded application? 那么,到底什么是多线程应用程序? Is it different from using Tasks ? 与使用Tasks有区别吗?

Tasks can be used to represent operations taking place on multiple threads, but they don't have to. 任务可以用来表示在多个线程上进行的操作,但不一定必须如此 One can write complex TPL applications that only ever execute in a single thread. 可以编写仅在单个线程中执行的复杂TPL应用程序。 When you have a task that, for example, represents a network request for some data, that task is not going to create additional threads to accomplish that goal. 例如,当您有一个任务代表某个数据的网络请求时,该任务将不会创建其他线程来实现该目标。 Such a program is (hopefully) asynchronous, but not necessarily mutlithreaded. 这样的程序(希望)是异步的,但不一定是多线程的。

Parallelism is doing more than one thing at the same time. 并行性在同一时间做很多事情。 This may or may not be the result of multiple threads. 这可能是也可能不是多个线程的结果。

Let's go with an analogy here. 让我们在这里进行类比。


Here is how Bob cooks dinner: 这是鲍勃做晚饭的方式:

  1. He fills a pot of water, and boils it. 他装满一锅水,煮沸。
  2. He then puts pasta in the water. 然后,他将面食放入水中。
  3. He drains the pasta when its done. 完成后,他将面食沥干。
  4. He prepares the ingredients for his sauce. 他准备酱料。
  5. He puts all of the ingredients for his sauce in a saucepan. 他把所有调味料放在一个锅里。
  6. He cooks his sauce. 他煮酱汁。
  7. He puts his sauce on his pasta. 他把酱汁放在意大利面上。
  8. He eats dinner. 他吃晚餐。

Bob has cooked entirely synchronously with no multithreading, asynchrony, or parallelism when cooking his dinner. 鲍勃在做饭时完全同步烹饪,没有多线程,异步或并行性。


Here is how Jane cooks dinner: 这是简做饭的方法:

  1. She fills a pot of water and starts boiling it. 她装满一锅水,开始煮沸。
  2. She prepares the ingredients for her sauce. 她准备酱料。
  3. She puts the pasta in the boiling water. 她把面食放在沸水中。
  4. She puts the ingredients in the saucepan. 她把食材放在锅里。
  5. She drains her pasta. 她排干意大利面。
  6. She puts the sauce on her pasta. 她把酱汁放在意大利面上。
  7. She eats her dinner. 她吃晚餐。

Jane leveraged asynchronous cooking (without any multithreading) to achieve parallelism when cooking her dinner. 简在烹饪晚餐时利用异步烹饪(没有任何多线程)来实现并行性。


Here is how Servy cooks dinner: 以下是Servy烹饪晚餐的方式:

  1. He tells Bob to boil a pot of water, put in the pasta when ready, and serve the pasta. 他告诉鲍勃烧开一锅水,准备好后放入意大利面,然后将其送达意大利面。
  2. He tells Jane to prepare the ingredients for the sauce, cook it, and then serve it over the pasta when done. 他告诉简为酱汁准备原料,将其煮熟,然后在面食上食用。
  3. He waits for Bob and Jane to finish. 他等待鲍勃和简完成。
  4. He eats his dinner. 他吃晚餐。

Servy leveraged multiple threads (workers) who each individually did their work synchronously, but who worked asynchronously with respect to each other to achieve parallelism. Servy利用了多个线程(工作人员),每个线程各自独立地同步完成工作,但彼此异步工作以实现并行性。

Of course, this becomes all the more interesting if we consider, for example, whether our stove has two burners or just one. 当然,如果我们考虑例如炉子有两个燃烧器还是只有一个燃烧器,这将变得更加有趣。 If our stove has two burners then our two threads, Bob and Jane, are both able to do their work without getting in each others way, much. 如果我们的火炉有两个燃烧器,那么我们的两个线程,鲍勃和简,就能在不互相干扰的情况下完成工作。 They might bump shoulders a bit, or each try to grab something from the same cabinet every now and then, so they'll each be slowed down a bit , but not much. 他们可能会碰到一点肩膀,或者每个人都时不时尝试从同一个柜子里拿东西,所以他们每个人都会放慢一点 ,但幅度不大。 If they each need to share a single stove burner though then they won't actually be able to get much done at all whenever the other person is doing work. 如果他们每个人都需要共享一个炉灶,那么无论何时对方工作时,他们实际上根本无法完成很多工作。 In that case, the work won't actually get done any faster than just having one person doing the cooking entirely synchronously, like Bob does when he's on his own. 在那种情况下,完成工作实际上不会比让一个人完全同步地做饭快得多,就像鲍勃独自一人做的那样。 In this case we are cooking with multiple threads, but our cooking isn't parallelized . 在这种情况下,我们正在使用多线程烹饪但是我们的烹饪不是并行的 Not all multithreaded work is actually parallel work . 并非所有的多线程工作实际上都是并行工作 This is what happens when you are running multiple threads on a machine with one CPU. 这是在具有一个CPU的计算机上运行多个线程时发生的情况。 You don't actually get work done any faster than just using one thread, because each thread is just taking turns doing work. 实际上,您完成工作的速度不会比仅使用一个线程快,因为每个线程只是轮流进行工作。 (That doesn't mean multithreaded programs are pointless on one cores CPUs, they're not, it's just that the reason for using them isn't to improve speed.) (这并不意味着多线程程序在一个内核CPU上是没有意义的,不是,不是,只是使用它们的原因并不是为了提高速度。)


We can even consider how these cooks would do their work using the Task Parallel Library, to see what uses of the TPL correspond to each of these types of cooks: 我们甚至可以考虑使用任务并行库来考虑这些厨师的工作方式,以了解TPL的用法与以下每种厨师相对应:

So first we have bob, just writing normal non-TPL code and doing everything synchronously: 因此,首先我们有了bob,只需编写普通的非TPL代码并同步进行所有操作即可:

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

Then we have Jane, who starts two different asynchronous operations, then waits for both of them after starting each of them to compute her result. 然后,我们有了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);
    }
}

As a reminder here, Jane is using the TPL, and she's doing much of her work in parallel, but she's only using a single thread to do her work. 在此提醒一下,Jane正在使用TPL,并且她并行执行许多工作,但是她仅使用一个线程来完成工作。

Then we have Servy, who uses Task.Run to create a task that represents doing work in another thread . 然后,我们有Servy,他使用Task.Run创建一个代表在另一个线程中进行工作的任务。 He starts two different workers, has them each both synchronously do some work, and then waits for both workers to finish. 他启动了两个不同的工人,让他们两个都同时做一些工作,然后等待两个工人完成。

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

A Task is a promise for future work to be completed. Task是对未来工作的承诺。 When using it, you can use it for I/O based work, which does not require you to be using multiple threads for code execution. 当使用它,你可以使用它的I/O based的工作,它不需要你使用多线程的代码执行是。 A good example is using C# 5's feature of async/await with HttpClient which does network based I/O work. 一个很好的例子是将HttpClient与C#5的async/await结合HttpClient ,该功能可进行基于网络的I / O工作。

However, you can take advantage of the TPL to do multithreaded work. 但是,您可以利用TPL进行多线程工作。 For example, when using Task.Run or Task.Factory.Startnew to start a new task, behind the scenes work gets queued for you on the ThreadPool , which the TPL abstracts away for us, allowing you to use multiple threads. 例如,当使用Task.RunTask.Factory.Startnew开始一个新的任务,工作被排队等待你在幕后ThreadPool ,其中TPL抽象了我们,让您使用多个线程。

A common scenario for using multiple threads is when you have CPU bound work which can be done simultaneously (in parallel). 使用多个线程的常见情况是当您具有可以同时(并行)完成的CPU绑定工作时。 Working with a multithreaded application comes with great responsibility. 使用多线程应用程序负有重大责任。

So we see that working with the TPL dosen't necasserly mean using multiple threads, but you definitely can leverage it to do multithreading. 因此,我们看到使用TPL并不一定意味着要使用多个线程,但是您绝对可以利用它来进行多线程处理。

Q: But, just you have used TPL means that you have written an multithreaded application? 问:但是,仅使用TPL就意味着您编写了多线程应用程序?

Smart question, Task != Multi-threaded, Using TaskCompletionSource you can create a Task which may execute in single thread(may be UI thread only) itself. 聪明的问题,任务!=多线程,使用TaskCompletionSource可以创建一个可以在单线程(仅UI线程)中执行的Task

Task is just an abstraction over an operation which may complete in future. Task只是对将来可能完成的操作的抽象。 It doesn't mean code is multi-threaded. 这并不意味着代码是多线程的。 Usually Task involves multi-threading, not necessarily always. 通常, Task涉及多线程,但不一定总是如此。

And keep in mind only with knowledge of TPL you can't say that you know multi-threading. 并且仅记住TPL知识就不能说您知道多线程。 There are many concepts you need to cover. 您需要涵盖许多概念。

  • Thread 线
  • Synchronization primitives 同步原语
  • Thread safety 线程安全
  • Asynchronous programming 异步编程
  • Parallel programming which is a part of TPL. 并行编程是TPL的一部分。
  • and lot more ... 还有更多...

and of course Task parallel library too. 当然还有Task并行库。

Note: this is not the full list, these are just from top of my head. 注意:这不是完整列表,这些只是我的脑袋。


Resources to learn: 学习资源:

For threading alone, I suggest http://www.albahari.com/threading/ 对于单独的线程,我建议http://www.albahari.com/threading/

For video tutorials I suggest Pluralsight . 对于视频教程,我建议使用Pluralsight It is paid but worth the cost. 它已支付,但值得。

Last but not least: Yes of course, It is Stackoverflow . 最后但并非最不重要的一点:当然可以,它是Stackoverflow

My 5 cents: you don't have to engage threads explicitly with Task.Run or Task.Factory.StartNew to make your TPL application multithreaded. 我的5分钱:您不必显式地将线程与Task.RunTask.Factory.StartNew即可使您的TPL应用程序成为多线程。 Consider this: 考虑一下:

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

The code after await inside doAsync is executed concurrently on different threads. 后该代码awaitdoAsync是在不同的线程同时执行。 In a similar way, concurrency can be introduced with async sockets API, HttpClient , Stream.ReadAsync or anything else that uses thread pool (including IOCP pool threads). 以类似的方式,可以通过异步套接字API, HttpClientStream.ReadAsync或使用线程池(包括IOCP池线程)的任何其他事物来引入并发。

Figuratively speaking, every .NET application is allready multithreaded, because the Framework uses ThreadPool extensively. 形象地讲,每个.NET应用程序都已经是多线程的,因为Framework广泛使用ThreadPool Even a simple console application will show more than one thread for System.Diagnostics.Process.GetCurrentProcess().Threads.Count . 甚至一个简单的控制台应用程序也将显示System.Diagnostics.Process.GetCurrentProcess().Threads.Count多个线程。 Your interviewer should have asked you about whether you have written concurrent (or parallel) code. 您的面试官应该问过您是否编写了并行 (或并行)代码。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM