关于c#:任务与线程差异

Task vs Thread differences

本问题已经有最佳答案,请猛点这里访问。

我不熟悉并行编程。在.NET中有两个类:TaskThread

所以,我的问题是:

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

  • 读这个。
  • 除非需要线程,否则首选任务。线程需要资源(1MB堆栈(在.NET提交中)、线程内核对象等)。任务也作为单独的线程并行运行,但它是一个系统线程池线程,由系统考虑到CPU核心等进行优化,用于在系统中运行许多任务。除此之外,任务完成时可以返回一个对象,因此有很好的方法知道并行执行的结果是什么。
  • @abhijitkadam当你说"系统"时,你指的是.NET框架吗?
  • 虽然这是一本有趣的书,@moonnight,但读一本关于穿线的书对于这样一个问题来说有点过分了。
  • @这是一本书中的一章。


Thread是一个较低级的概念:如果您直接启动一个线程,那么您知道它将是一个单独的线程,而不是在线程池中执行。

不过,Task不仅仅是"在哪里运行某些代码"的抽象概念,它实际上只是"对未来结果的承诺"。因此,作为一些不同的例子:

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

注意,Task抽象对于C_5中的异步支持至关重要。

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

  • 即使运行类似消息循环的进程,也要使用任务而不是线程?
  • @索莫斯:可能吧——你可以把它创建为"长时间运行"的,它最终会得到一个专用的线程,但这意味着在整个过程中只使用一个抽象。
  • @Jonskeet还值得一提的是,在ASP.NET中,new Thread()不处理线程池线程,而Task使用线程池线程i.stack.imgur.com/o8anu.jpg
  • @Royinamir:这并不是一个ASP.NET特有的东西——在某些情况下,当您启动一个任务时,如果您指定它将是一个长期运行的任务,它可能会使用一个非线程池线程。
  • @当你真的需要用现代C语言开始自己的思路时,你能举出几个例子吗?
  • @莱昂尼德:不是随便的……它确实避免了任务调度器需要参与到所有…但是的,这些天我会坚持任务。快跑。
  • @Leonidvasilyev:不是直接的任务相关的(这是在引入任务之前),但是我做了一些图像分析工作后,确实让线程池处于饥饿状态。我使用线程池进行对象检测(实际上太多了),但使用的是一个也使用thread pool的sdk来推送结果。这导致程序锁定,因为所有线程都很忙。
  • 如果我想在实时系统中使用Task,我需要暂停和恢复、取消和停止,Task是否在这种严酷的环境中工作?
  • @锡安皮:不,埃多克斯1〔1〕没有暂停的概念。


来源

螺纹

线程表示一个实际的操作系统级线程,具有自己的堆栈和内核资源。(从技术上讲,clr实现可以使用fibers,但没有现有的clr可以这样做)线程允许最高程度的控制;可以中止()或挂起()或恢复()线程(尽管这是一个非常糟糕的主意),可以观察其状态,并且可以设置线程级属性,如堆栈大小、单元状态或区域性。

线程的问题是操作系统线程的开销很高。您拥有的每个线程都会为其堆栈消耗大量的内存,并随着线程之间的处理器上下文切换增加额外的CPU开销。相反,当工作可用时,最好让一个小的线程池执行代码。

有时没有备用线程。如果需要指定名称(用于调试)或单元状态(用于显示UI),则必须创建自己的线程(请注意,拥有多个UI线程通常是一个坏主意)。此外,如果要维护一个由单个线程拥有且只能由该线程使用的对象,则更容易显式地为其创建线程实例,以便轻松检查尝试使用该对象的代码是否在正确的线程上运行。

线程池

threadpool是由clr维护的线程池的包装器。threadpool根本不给您任何控制权;您可以在某个时刻提交要执行的工作,并且可以控制池的大小,但不能设置任何其他内容。您甚至无法知道池何时开始运行您提交给它的工作。

使用threadpool可以避免创建过多线程的开销。但是,如果向线程池提交的长时间运行的任务太多,则线程池可能会满,提交的后续工作可能会等待较早的长时间运行项目完成。此外,threadpool不提供查找工作项何时完成的方法(与thread.join()不同),也不提供获取结果的方法。因此,threadpool最好用于调用方不需要结果的短操作。

任务

最后,来自任务并行库的任务类提供了两个世界中最好的。与threadpool一样,任务不会创建自己的OS线程。相反,任务由taskscheduler执行;默认的scheduler只在threadpool上运行。

与threadpool不同,task还允许您查明它何时完成,并(通过一般任务)返回结果。可以对现有任务调用ContinueWith(),使其在任务完成后运行更多代码(如果任务已经完成,则会立即运行回调)。如果任务是通用的,continueWith()将向您传递任务的结果,从而允许您运行更多使用它的代码。

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

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

所有新的高级并发API,包括parallel.for*()方法、plinq、c_5 await和bcl中的现代异步方法,都是基于任务构建的。

结论

归根结底,任务几乎总是最好的选择;它提供了更强大的API,并避免浪费OS线程。

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

  • 我从这一反应中学到了最多。谢谢您。
  • 一组必须在应用程序的整个生命周期内运行的线程如何?使用具有长时间运行选项的任务是否仍然更可取?
  • 异步/异步API调用属于哪个类别?
  • @XFx你得到答案了吗?如果我们用无限循环写出tpl怎么办?不过,这不是一个好方法。
  • @FaizanMubasher不幸的是没有,但是我已经运行了一些测试(使用生产程序),我无法检测到从线程到任务的任何负面影响。


通常你会听到任务是一个比线程更高层次的概念…这句话的意思是:

  • 不能使用abort/threadabortedException,您应该支持在您的"业务代码"中取消事件,定期测试token.IsCancellationRequested标志(也要避免长时间或超时连接,例如到db,否则您将无法测试此标志)。由于类似的原因,Thread.Sleep(delay)呼叫应替换为Task.Delay(delay, token)呼叫。

  • 没有线程的SuspendResume方法的任务功能。任务的实例也不能重用。

  • 但是你有两个新的工具:

    a)延续

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // 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)嵌套/子任务

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    //StartNew - starts task immediately, parent ends whith child
    var parent = Task.Factory.StartNew
    (() => {
              var child = Task.Factory.StartNew(() =>
             {
             //...
             });
          },  
          TaskCreationOptions.AttachedToParent
    );
  • 所以系统线程完全隐藏在任务中,但任务的代码仍然在具体的系统线程中执行。系统线程是任务的资源,当然,在任务并行执行的框架下仍然存在线程池。线程如何执行新任务可能有不同的策略。另一个共享资源任务调度程序关心它。任务调度器解决的一些问题1)更喜欢在同一线程中执行任务及其二次执行,从而最大限度地降低切换成本(即内联执行)2)更喜欢按启动顺序执行任务(即首选公平性)3)更有效地在非活动线程之间分配任务,这取决于"任务活动的先验知识"—a你在偷东西。重要提示:一般来说,"异步"与"并行"不同。使用TaskScheduler选项,可以在一个线程中同步执行异步任务。为了表示并行代码执行,可以使用更高的抽象(而不是任务):Parallel.ForEachPLINQDataflow

  • 任务与c异步/等待功能(也称为Promise Model)集成,例如在requestButton.Clicked += async (o, e) => ProcessResponce(await client.RequestAsync(e.ResourceName));中,执行client.RequestAsync不会阻塞UI线程。重要提示:在引擎盖下,Clicked委托调用是绝对常规的(所有线程都由编译器完成)。

  • 这足以做出选择。如果您需要支持取消调用倾向于挂起(例如超时连接)的旧API的功能,并且在这种情况下支持thread.abort(),或者如果您正在创建多线程后台计算并希望使用suspend/resume优化线程之间的切换,这意味着手动管理并行执行-请使用读。否则,转到任务,因为它们可以让您轻松地对任务组进行操作,集成到语言中,并使开发人员更有效率-任务并行库(TPL)。

    • 注-Thread.Abort也不适用于大多数长操作,如db连接。它只能在托管代码中中止,而大多数长时间等待的内容都停留在本机代码中(例如等待句柄、I/O操作…)。唯一的好处是,它可以在托管代码中的任何地方中止,相对安全(例如,不在finally子句等中),因此它将有助于防止无限循环等错误。不过,在生产级代码中使用它并没有多大意义。
    • 以东十一〔五〕是恶的,应尽可能避免。在线程可能中止的情况下运行的代码很难正确地解释。这不值得,只需检查某种旗帜。(我建议使用CancellationTokenAPI,即使您不使用任务)
    • 这样做是错误的,如果可能的话,应该用Task.Delay(delay, token);替换Thread.Sleep(delay),然后用Task.Delay(delay, token).Wait();替换,这与Thread.Sleep(delay)相同,或者用await Task.Delay(delay, token)


    Thread类用于在Windows中创建和操作线程。

    Task表示一些异步操作,是任务并行库的一部分,这是一组用于异步和并行运行任务的API。

    在旧的时代(即在tpl之前),使用Thread类是在后台或并行运行代码的标准方法之一(更好的选择通常是使用ThreadPool,但是这很麻烦,有几个缺点,尤其是创建一个全新的t的性能开销。hread在后台执行任务。

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