任务和线程有什么区别?


378

在C#4.0中,我们TaskSystem.Threading.Tasks命名空间中。Thread和之间的真正区别是什么Task。为了我自己的学习,我做了一些示例程序(来自MSDN的帮助)

Parallel.Invoke 
Parallel.For 
Parallel.ForEach 

但是有很多疑问,因为这个想法不太清楚。

我最初在Stackoverflow中搜索了类似类型的问题,但可能与此问题标题有关,我无法获得相同的问题。如果有人知道先前在此处发布的相同类型的问题,请提供链接的参考。


8
线程运行任务
pm100,2015年

Answers:


314

一项任务是您要完成的事情。

线程是执行该任务的许多可能的工作程序之一。

用.NET 4.0术语来说,任务表示异步操作。线程用于通过将工作分解为多个块并分配给单独的线程来完成该操作。


您能否提供一个完成任务的线程的基本示例?我不知道这些线程是否正在执行彼此独立的工作,或者它们是否进行一些团队合作计算?
pensum

这两种情况都是可能的:在最佳情况下,线程可以独立工作,而无需与其他线程同步。在实践中,锁用于协调线程。
米奇麦特

451

用计算机科学的术语来说,a Task未来希望。(有些人用同义的方式使用了这两个术语,有些人使用了不同的术语,没有人可以就一个精确的定义达成一致。)基本上,一个Task<T>“承诺”可以返回您一个T亲爱的,但不是现在,亲爱的,我有点忙,为什么不你晚点回来吗?

A Thread是兑现承诺的一种方式。但是,并非每个人都Task需要全新的Thread。(实际上,创建线程通常是不可取的,因为这样做比重用线程池中的现有线程要昂贵得多。稍后会更多。)如果您要等待的值来自文件系统或数据库或网络,那么当线程可以为其他请求提供服务时,就不需要线程来等待数据。取而代之的是,它们Task可能会注册一个回调以在准备好值时接收它们。

特别是,Task没有说明为什么花这么长时间才能返回该值。这可能是因为它需要很长的时间来计算,或者它可能需要花费很长的时间来获取。仅在前一种情况下,您Thread才能使用a 来运行Task。(在.NET中,线程非常昂贵,因此,您通常希望尽可能避免使用它们,并且仅在要在多个CPU上运行多个繁重计算的情况下才真正使用它们。例如,在Windows中,线程的重为12 KiByte(我认为),在Linux中,线程重达4 KiByte,在Erlang / BEAM中甚至只有400 Byte。在.NET中,它是1 MiByte!)


29
有趣的是,TPL(任务并行库)的早期预览版中包含Task和Future <T>。然后,将Future <T>重命名为Task <T>。:)
Lee Campbell

23
您如何计算.NET的1 MB?
dvallejo 2012年

5
@DanVallejo:在TPL设计团队的一次采访中提到了这个数字。我不能告诉你几年前我看过谁说的或者是哪一次的采访。
约尔格W¯¯米塔格

9
@RIPUNJAYTRIPATHI当然可以,但不必是另一个线程,它可以是最初请求工作的线程。
克里斯·皮特曼

7
.NET仅在Windows上使用Windows线程,因此大小是相同的-默认情况下,两者的虚拟内存通常为1 MiB。页面大小的块(通常为64 kiB)中按需使用物理内存,本机代码也是如此。最小线程堆栈大小取决于操作系统-例如,对于Vista为256 kiB。在x86 Linux上,默认值通常为2 MiB-同样,以页面大小的块分配。(简化)Erlang每个进程仅使用一个系统线程,这400个字节引用类似于.NET的内容Task
a安

39

线

裸机,您可能不需要使用它,您可能可以使用LongRunning任务,并从.NET Framework 4(2002年2月)及更高版本(也包括.NET)中包含的TPL-Task Parallel Library中受益。核心)。

任务

线程之上的抽象。它使用线程池(除非您将任务指定为LongRunning操作,否则,将在后台为您创建一个新线程)。

线程池

顾名思义:线程池。.NET框架是否为您处理了有限数量的线程。为什么?因为在只有8个内核的处理器上打开100个线程来执行昂贵的CPU操作绝对不是一个好主意。框架将为您维护该池,重用线程(而不是在每次操作时都创建/杀死线程),并以不消耗CPU的方式并行执行其中的一些线程。

OK,但是什么时候使用每个?

在简历中:始终使用任务。

Task是一种抽象,因此使用起来容易得多。我建议您始终尝试使用任务,如果遇到一些问题,需要自己处理线程(大约1%的时间),请使用线程。

但是请注意:

  • I / O绑定:对于I / O绑定操作(数据库调用,读/写文件,API调用等),请避免使用常规任务,而应使用LongRunning任务(或线程)(如果需要)。因为使用任务将导致您进入一个线程池,该线程池中有几个线程正在忙碌,而还有许多其他任务在等待轮换使用该池。
  • CPU绑定:对于CPU绑定操作,只需使用常规任务(内部将使用线程池)就可以了。

稍加修正,线程不是“裸机”。它是由OS实现的,大多数实现都是基于CPU和CS的功能,但它们不是由硬件实现的。
Tomer W

7

您可以使用Task指定你想要做什么,然后附上TaskThread。这样Task就可以在新创建的代码中执行,Thread而不是在GUI线程上执行。

使用TaskTaskFactory.StartNew(Action action)。在这里,您将执行一个委托,因此,如果您不使用任何线程,它将在同一线程(GUI线程)中执行。如果提到线程,则可以Task在其他线程中执行。这是不必要的工作,因为您可以直接执行委托,也可以将该委托附加到线程上并在该线程中执行该委托。所以不要使用它。只是没有必要。如果您打算优化软件,那么这是一个很好的选择。

**请注意,Actiondelegate


6

除了上述几点,最好知道:

  1. 默认情况下,任务是后台任务。您不能有前台任务。另一方面,线程可以是背景或前景(使用IsBackground属性更改行为)。
  2. 在线程池中创建的任务会回收线程,这有助于节省资源。因此,在大多数情况下,任务应该是您的默认选择。
  3. 如果操作很快,那么最好使用任务而不是线程。对于长时间运行的操作,与线程相比,任务没有提供太多优势。

4

我通常使用Task与Winforms和简单的后台工作程序进行交互,以使其不会冻结UI。这是我更喜欢使用的示例Task

private async void buttonDownload_Click(object sender, EventArgs e)
{
    buttonDownload.Enabled = false;
    await Task.Run(() => {
        using (var client = new WebClient())
        {
            client.DownloadFile("http://example.com/file.mpeg", "file.mpeg");
        }
    })
    buttonDownload.Enabled = true;
}

VS

private void buttonDownload_Click(object sender, EventArgs e)
{
    buttonDownload.Enabled = false;
    Thread t = new Thread(() =>
    {
        using (var client = new WebClient())
        {
            client.DownloadFile("http://example.com/file.mpeg", "file.mpeg");
        }
        this.Invoke((MethodInvoker)delegate()
        {
            buttonDownload.Enabled = true;
        });
    });
    t.IsBackground = true;
    t.Start();
}

区别在于您不需要使用MethodInvoker代码较短的代码。


4

任务就像您要执行的操作一样,线程可帮助您通过多个流程节点管理这些操作。任务是一个轻量级的选择,因为线程可能导致复杂的代码管理
,我会建议从MSDN读取(在世界上最好的)总

任务

线


3

可以将Task看作是异步且并行执行某些任务的便捷方法。

通常,一个任务就是您所需要的,我不记得是否曾经将线程用于实验以外的用途。

您可以使用线程(很费力)来完成与完成任务相同的任务。

线

int result = 0;
Thread thread = new System.Threading.Thread(() => { 
    result = 1; 
});
thread.Start();
thread.Join();
Console.WriteLine(result); //is 1

任务

int result = await Task.Run(() => {
    return 1; 
});
Console.WriteLine(result); //is 1

默认情况下,任务将使用线程池,因为创建线程可能会很昂贵,因此可以节省资源。您可以将Task看作是线程的更高层次的抽象。

正如本文所指出的,任务通过线程提供了以下强大的功能。

  • 调整了任务以利用多核处理器。

  • 如果系统有多个任务,那么它将在内部使用CLR线程池,因此不会产生与使用线程创建专用线程相关的开销。还可以减少多个线程之间的上下文切换时间。

  • Task可以返回结果。没有直接的机制可以从线程返回结果。
  • 等待一组任务,没有信令构造。

  • 我们可以将任务链接在一起,一个接一个地执行。

  • 当一个任务从另一任务开始时,建立父子关系。

  • 子任务异常可以传播到父任务。

  • 任务通过使用取消令牌来支持取消。

  • 使用'async'和'await'关键字,异步实现在任务中很容易。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.