【问题标题】:What is the difference between task and thread?任务和线程有什么区别?
【发布时间】:2021-11-07 13:14:22
【问题描述】:

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

Parallel.Invoke 
Parallel.For 
Parallel.ForEach 

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

我最初在 Stackoverflow 中搜索过类似类型的问题,但可能是这个问题标题我无法得到相同的问题。如果有人知道之前在这里发布了相同类型的问题,请提供链接的参考。

【问题讨论】:

  • 线程运行任务

标签: c# multithreading c#-4.0 task-parallel-library terminology


【解决方案1】:

在计算机科学术语中,Task未来承诺。 (有些人将这两个术语用作同义词,有些人用不同的方式,没有人能就精确的定义达成一致。)基本上,Task<T>“承诺”返回T,但不正确现在亲爱的,我有点忙,你为什么不晚点回来?

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

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

【讨论】:

  • 有趣的是,在 TPL(任务并行库)的早期预览版本中有 Task 和 Future。 Future 随后被重命名为 Task。 :)
  • 您是如何计算 .NET 的 1 MB 的?
  • @DanVallejo:在接受 TPL 设计团队采访时提到了这个数字。我不能告诉你是谁说的或者是哪个采访,我几年前看过的。
  • @RIPUNJAYTRIPATHI 当然可以,但它不需要是另一个线程,它可能是首先请求工作的线程。
  • .NET 仅在 Windows 上使用 Windows 线程,因此大小是相同的 - 默认情况下,两者通常都是 1 MiB 的虚拟内存。在页面大小的块(通常为 64 kiB)中根据需要使用物理内存,与本机代码相同。最小线程堆栈大小取决于操作系统 - 例如,Vista 为 256 kiB。在 x86 Linux 上,默认值通常为 2 MiB - 再次以页面大小的块分配。 (简化)Erlang 每个进程只使用一个系统线程,这 400 个字节指的是类似于 .NETs Task 的东西。
【解决方案2】:

任务是你想要完成的事情。

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

在 .NET 4.0 术语中,Task 表示异步操作。线程用于通过将工作分成块并分配给单独的线程来完成该操作。

【讨论】:

  • 您能否提供一个用于完成任务的线程的基本示例?我不知道线程是在做彼此独立的工作还是在做一些团队合作计算?
  • 两种情况都是可能的:在最佳情况下,线程独立工作,无需与其他线程同步。在实践中,锁用于协调线程。
【解决方案3】:

线程

裸机,您可能不需要使用它,您可能可以使用LongRunning 任务并从 .NET Framework 4 中包含的 TPL - 任务并行库(2002 年 2 月)中受益及以上(还有 .NET Core)。

任务

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

线程池

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

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

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

Task 是一种抽象,所以它更容易使用。我建议您始终尝试使用任务,如果您遇到需要自己处理线程的问题(可能有 1% 的时间),请使用线程。

但请注意:

  • I/O 绑定:对于 I/O 绑定操作(数据库调用、读/写文件、API 调用等),请避免使用普通任务,使用 LongRunning 任务(或线程,如果你需要)。因为使用任务会导致您进入一个线程池,其中有几个线程很忙,还有很多其他任务在等待轮到它占用池。
  • CPU 绑定:对于 CPU 绑定操作,只需使用普通任务(内部将使用线程池)并感到满意。

【讨论】:

  • 轻微修正,线程不是“裸机”。它是由操作系统实现的,大多数实现依赖于 CPU 和 CS 的特性,但它们不是由硬件实现的。
【解决方案4】:

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

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

【讨论】:

    【解决方案5】:

    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 视为线程上更高级别的抽象。

    正如this article 所指出的,Task 在 Thread 上提供了以下强大的功能。

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

    • 如果系统有多个任务,那么它会使用 CLR 线程池 在内部,因此没有与创建相关的开销 使用 Thread 的专用线程。也减少了上下文 多线程切换时间。

    • 任务可以返回结果。没有从线程返回结果的直接机制。

    • 等待一组任务,没有信号构造。

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

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

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

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

    • Task 中的异步实现很简单,使用asyncawait关键字。

    【讨论】:

      【解决方案6】:

      我通常使用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 和更短的代码。

      【讨论】:

        【解决方案7】:

        您可以使用Task 指定您要执行的操作,然后将Task 附加到Thread。这样Task 将在新创建的Thread 中执行,而不是在GUI 线程上。

        TaskTaskFactory.StartNew(Action action) 一起使用。在这里,您执行一个委托,因此如果您不使用任何线程,它将在同一个线程(GUI 线程)中执行。如果你提到一个线程,你可以在另一个线程中执行这个Task。这是一项不必要的工作,因为您可以直接执行委托或将该委托附加到线程并在该线程中执行该委托。所以不要使用它。这只是不必要的。如果您打算优化您的软件,这是一个很好的删除候选者。

        **请注意Actiondelegate

        【讨论】:

          【解决方案8】:

          任务就像您要执行的操作。线程通过多个进程节点帮助管理这些操作。任务是一个轻量级选项,因为线程会导致复杂的代码管理。
          我建议您始终阅读 MSDN(世界上最好的)

          Task

          Thread

          【讨论】:

            猜你喜欢
            • 2011-03-03
            • 1970-01-01
            • 2017-02-10
            相关资源
            最近更新 更多