任务与线程差异

时间:2012-11-17 08:58:37

标签: c# .net multithreading task

我是并行编程的新手。 .NET中有两个类:TaskThread

所以,我的问题是:

  • 这些课程有什么区别?
  • 何时使用Thread和何时Task
  • 更好

4 个答案:

答案 0 :(得分:375)

Thread是一个较低级别的概念:如果你直接启动一个线程,你知道它将是一个单独的线程,而不是在线程池等上执行

Task不仅仅是一个抽象的"在哪里运行一些代码"虽然 - 它真的只是"未来结果的承诺"。所以作为一些不同的例子:

  • Task.Delay不需要任何实际的CPU时间;它就像将来一个计时器设置为
  • WebClient.DownloadStringTaskAsync返回的任务不会在本地占用大量CPU时间;它表示可能将大部分时间花在网络延迟或远程工作上的结果(在Web服务器上)
  • Task.Run()确实返回的任务是说"我希望您单独执行此代码&#34 ;;该代码执行的确切线程取决于许多因素。

请注意,Task<T>抽象是C#5中异步支持的关键。

一般情况下,我建议您尽可能使用更高级别的抽象:在现代C#代码中,您应该很少需要显式启动自己的线程。

答案 1 :(得分:248)

Source

<强>发

Thread代表一个实际的OS级线程,具有自己的堆栈和内核资源。 (从技术上讲,CLR实现可以使用光纤,但现有的CLR不会这样做)线程允许最高程度的控制;你可以Abort()或Suspend()或Resume()一个线程(虽然这是一个非常糟糕的主意),你可以观察它的状态,你可以设置线程级属性,如堆栈大小,公寓状态或文化。

Thread的问题在于OS线程成本高昂。您拥有的每个线程都会为其堆栈消耗大量内存,并在线程之间切换处理器上下文时增加额外的CPU开销。相反,最好让一小部分线程在工作可用时执行代码。

有些时候没有替代线程。如果需要指定名称(用于调试目的)或单元状态(用于显示UI),则必须创建自己的线程(请注意,具有多个UI线程通常是个坏主意)。此外,如果您想维护一个由单个线程拥有并且只能由该线程使用的对象,那么为它显式创建一个Thread实例要容易得多,这样您就可以轻松检查尝试使用它的代码是否正在运行在正确的线程上。

<强>线程池

ThreadPool是CLR维护的线程池的包装器。 ThreadPool完全没有控制权;您可以提交工作以在某个时刻执行,并且您可以控制池的大小,但您不能设置其他任何内容。您甚至无法判断池何时开始运行您提交给它的工作。

使用ThreadPool可以避免创建太多线程的开销。但是,如果向线程池提交太多长时间运行的任务,它可能会变满,以后您提交的工作最终可能会等待较早的长时间运行的项目完成。此外,ThreadPool无法找出工作项何时完成(与Thread.Join()不同),也无法获得结果。因此,ThreadPool最适用于调用者不需要结果的短操作。

<强>任务

最后,任务并行库中的Task类提供了两全其美的功能。与ThreadPool一样,任务不会创建自己的OS线程。相反,任务由TaskScheduler执行;默认调度程序只在ThreadPool上运行。

与ThreadPool不同,Task还允许您查找完成的时间,并(通过通用任务)返回结果。您可以在现有任务上调用ContinueWith(),以便在任务完成后运行更多代码(如果它已经完成,它将立即运行回调)。如果任务是通用的,ContinueWith()将向您传递任务的结果,允许您运行更多使用它的代码。

您还可以通过调用Wait()同步等待任务完成(或者,对于通用任务,通过获取Result属性)。与Thread.Join()一样,这将阻止调用线程,直到任务完成。同步等待任务通常是个坏主意;它阻止调用线程执行任何其他工作,并且如果任务最终等待(甚至是异步)当前线程,也可能导致死锁。

由于任务仍然在ThreadPool上运行,因此它们不应该用于长时间运行的操作,因为它们仍然可以填满线程池并阻止新工作。相反,Task提供了一个LongRunning选项,它将告诉TaskScheduler启动一个新线程,而不是在ThreadPool上运行。

所有较新的高级并发API,包括Parallel.For *()方法,PLINQ,C#5等待,以及BCL中的现代异步方法都是基于Task构建的。

<强>结论

最重要的是,任务几乎总是最佳选择;它提供了更强大的API,避免浪费操作系统线程。

在现代代码中显式创建自己的线程的唯一原因是设置每线程选项,或维护需要维护自己身份的持久线程。

答案 2 :(得分:33)

通常你会听到任务是一个比线程更高级别的概念......这就是这句话的含义:

  1. 您不能使用Abort / ThreadAbortedException,您应该支持 在“业务代码”中取消事件,定期测试token.IsCancellationRequested标志(也避免长连接或无超时连接,例如db,否则你将永远不会有机会测试这个标志)。出于类似原因,Thread.Sleep(delay)调用应替换为Task.Delay(delay, token)调用。

  2. 任务中没有线程的SuspendResume方法功能。 任务实例无法重复使用

  3. 但是你有两个新工具:

    a)延续

    // continuation with ContinueWhenAll - execute the delegate, when ALL
    // tasks[] had been finished; other option is ContinueWhenAny
    
    Task.Factory.ContinueWhenAll( 
       tasks,
       () => {
           int answer = tasks[0].Result + tasks[1].Result;
           Console.WriteLine("The answer is {0}", answer);
       }
    );
    

    b)嵌套/子任务

    //StartNew - starts task immediately, parent ends whith child
    var parent = Task.Factory.StartNew
    (() => {
              var child = Task.Factory.StartNew(() =>
             {
             //...
             });
          },  
          TaskCreationOptions.AttachedToParent
    );
    
  4. 因此系统线程完全隐藏在任务之外,但仍然在具体的系统线程中执行任务的代码。 系统线程是任务的资源,当然还有任务并行执行的线程池。线程如何让新任务执行有不同的策略。另一个共享资源 TaskScheduler 关心它。 TaskScheduler 解决的一些问题1)更喜欢执行任务,并且在同一个线程中确定最小化转换成本 - 也就是内联执行)2)更喜欢按照它们的顺序执行任务开始 - 又名 PreferFairness 3)根据“任务活动的先验知识” - 又名工作窃取,在非活动线程之间更有效地分配任务。重要提示:通常“异步”与“并行”不同。使用TaskScheduler选项,您可以设置异步任务同步在一个线程中执行。要表达并行代码执行,可以使用更高的抽象(比任务):Parallel.ForEachPLINQDataflow

  5. 任务与C#async / await功能又名 Promise Model 集成,例如requestButton.Clicked += async (o, e) => ProcessResponce(await client.RequestAsync(e.ResourceName));执行client.RequestAsync不会阻止UI线程。重要提示:引擎Clicked委托调用是绝对定期的(所有线程都由编译器完成)。

  6. 这足以做出选择。如果您需要支持调用遗留API的取消功能(例如无超连接),并且此情况支持Thread.Abort(),或者您正在创建多线程后台计算并希望使用Suspend / Resume优化线程之间的切换,这意味着手动管理并行执行 - 留在Thread。否则,请转到“任务”,因为它们可以让您轻松操作它们,将它们集成到语言中并提高开发人员的工作效率 - Task Parallel Library (TPL)

答案 3 :(得分:29)

Thread类用于在Windows中创建和操作thread

Task代表一些异步操作,是Task Parallel Library的一部分,ThreadPool是一组用于异步和并行运行任务的API。

在旧时代(即TPL之前)过去,使用Thread类是在后台或并行运行代码的标准方法之一(更好的选择通常是使用{{3}}),但这很麻烦并且有一些缺点,其中最重要的是创建一个全新线程以在后台执行任务的性能开销。

现在使用任务和TPL在90%的时间里都是一个更好的解决方案,因为它提供了抽象,可以更有效地使用系统资源。我想有一些场景你想要显式控制你运行代码的线程,但一般来说,如果你想异步运行某些东西,你的第一个调用端口应该是TPL。