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

时间:2014-05-23 15:35:21

标签: c# multithreading .net-4.0 task-parallel-library multitasking

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

问:你有没有编写多线程应用程序?

答:是的

问:关心更多解释?

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

问:但是,您使用TPL表示您已经编写了multithreaded个应用程序?

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

那么,什么是多线程应用程序呢?它与使用Tasks不同吗?

4 个答案:

答案 0 :(得分:98)

任务可以用于表示在多个线程上发生的操作,但它们没有。一个可以编写只能在单个线程中执行的复杂TPL应用程序。例如,当您有一项任务表示某些数据的网络请求时,该任务将创建其他线程来实现该目标。这样的程序(希望)是异步的,但不一定是mutlithreaded。

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

我们在这里进行类比。


这是鲍勃如何做晚餐的方式:

  1. 他把一锅水倒进去煮沸。
  2. 然后他将意大利面放入水中。
  3. 当它完成后,他将意大利面排干。
  4. 他为酱汁准备食材。
  5. 他把酱汁的所有成分都放在一个平底锅里。
  6. 他煮酱汁。
  7. 他把酱汁放在意大利面上。
  8. 他吃晚饭。
  9. 鲍勃完成了同步烹饪,在烹饪晚餐时没有多线程,异步或并行性。


    以下是Jane如何做饭:

    1. 她倒了一壶水,开始煮沸。
    2. 她为酱汁准备食材。
    3. 她将意大利面放入沸水中。
    4. 她把食材放在平底锅里。
    5. 她排出了她的意大利面。
    6. 她把酱汁放在她的面食上。
    7. 她吃晚饭。
    8. Jane利用异步烹饪(没有任何多线程)来烹饪晚餐时实现并行性。


      以下是Servy如何烹饪晚餐:

      1. 他告诉鲍勃煮一壶水,准备好时放入意大利面,并提供面食。
      2. 他告诉Jane准备酱汁的配料,做饭,然后在意大利面上做好。
      3. 他等待鲍勃和简完成。
      4. 他吃晚饭。
      5. 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);
            }
        }
        

答案 1 :(得分:4)

Task是对未来工作的承诺。使用它时,您可以将其用于I/O based工作,要求您使用多个线程执行代码。一个很好的例子是使用C {5的async/awaitHttpClient的功能来完成基于网络的I / O工作。

但是,您可以利用TPL执行多线程工作。例如,当使用Task.RunTask.Factory.Startnew开始新任务时,幕后工作会在ThreadPool上为您排队,TPL为我们摘录,允许你使用多个线程。

使用多个线程的常见情况是,当您具有可以同时(并行)完成的CPU绑定工作时。使用多线程应用程序需要承担很大的责任。

所以我们看到使用TPL并不意味着使用多个线程,但你绝对可以利用它来进行多线程。

答案 2 :(得分:3)

  问:但是,只是你使用过TPL意味着你已经写过了   多线程应用程序?

智能问题,任务!=多线程,使用TaskCompletionSource您可以创建一个Task,它可以在单个线程中执行(可能只是UI线程)本身。

Task只是对未来可能完成的操作的抽象。这并不意味着代码是多线程的。通常Task涉及多线程,不一定总是如此。

请记住只有TPL的知识,你不能说你知道多线程。您需要涵盖许多概念。

  • 同步原语
  • 线程安全
  • 异步编程
  • 并行编程,是TPL的一部分。
  • 以及更多......

当然还有Task并行库。

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

<小时/> 学习资源:

仅针对线程,我建议http://www.albahari.com/threading/

对于视频教程,我建议Pluralsight。它是有偿但值得的。

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

答案 3 :(得分:1)

我的5美分:您不必明确地与Task.RunTask.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();

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

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