C#中多线程和多任务的区别

5

可能重复:
任务和线程之间有什么区别?

我知道这个问题的标题看起来像是一个重复的问题,但我已经阅读了所有与此主题相关的先前帖子,但仍然不太理解程序行为。

我目前正在编写一个检查大约1,000个电子邮件帐户的小程序。毫无疑问,我认为多线程或多任务是正确的方法,因为每个线程/任务的计算成本不高,但每个线程的持续时间严重依赖于网络I/O。

在这种情况下,我认为将线程/任务数设置为远远大于核心数量的数字(i5-750为四个)也是合理的。因此,我将线程或任务的数量设置为100。

使用Tasks编写的代码片段:

        const int taskCount = 100;
        var tasks = new Task[taskCount];
        var loopVal = (int) Math.Ceiling(1.0*EmailAddress.Count/taskCount);

        for (int i = 0; i < taskCount; i++)
        {
            var objContainer = new AutoCheck(i*loopVal, i*loopVal + loopVal);
            tasks[i] = new Task(objContainer.CheckMail);
            tasks[i].Start();
        }
        Task.WaitAll(tasks);

使用线程编写的相同代码片段:

使用线程编写的相同代码片段:

        const int threadCount = 100;
        var threads = new Thread[threadCount];
        var loopVal = (int)Math.Ceiling(1.0 * EmailAddress.Count / threadCount);

        for (int i = 0; i < threadCount; i++)
        {
            var objContainer = new AutoCheck(i * loopVal, i * loopVal + loopVal);
            threads[i] = new Thread(objContainer.CheckMail);
            threads[i].Start();
        }
        foreach (Thread t in threads)
            t.Join();
        runningTime.Stop();
        Console.WriteLine(runningTime.Elapsed);

那么这两者之间的本质区别是什么?

据我理解,TaskScheduler会将需要比硬件提供更多线程的任务暂停,直到有足够的线程可用于该任务。如果我理解正确,这个想法是,如果您在大约相同的时间启动了大量线程,它们将争夺硬件资源。而这种争斗在任务中不会发生。 - LightStriker
@Marc-AndréJutras 这只是使用默认任务调度程序启动的任务的行为,它只在线程池上运行它们。您可以有一些任务从不创建线程,或者根本不在任何其他线程中执行任何代码。 - Servy
你已经写了两个,为什么不测试一下两个呢? - Martin James
@MartinJames 嗯,它们都可以“工作”。如果你不知道要寻找什么,可能很难真正区分它们运行的不同之处。 - Servy
2个回答

8
任务并不一定对应线程。任务库会以比您的线程代码更高效的方式将任务调度到线程池线程中。
创建线程相当昂贵。任务将排队并重复使用线程,因此当线程等待网络IO时,它实际上可以被重用来执行另一个任务。空闲的线程是浪费资源。您只能执行与处理器核心数相对应的线程(同时),因此100个线程意味着在所有核心上至少进行25次上下文切换。
如果使用任务,请只排队所有1000个电子邮件处理任务而不是分批处理它们,然后让其运行。任务库将处理要在多少线程上运行它。

这全部适用于默认任务计划程序。您还可以使用自定义任务计划程序,具有从TaskCompletionSource对象生成的任务等。 - Servy
如果使用任务,只需将所有1000个电子邮件处理任务排队,而不是批量处理它们,然后让它运行。任务库将处理要在其上运行多少线程。 这是否意味着我只需将“taskCount”值更改为1000并让其运行? - derekhh
是的,他在示例中使用了默认的任务调度程序 :) - Mike Marynowski
@derekhh 是的,你可以这样做,任务库会为你安排这1000个任务,并将其分配到适当数量的线程中。 - Mike Marynowski

0

简单来说,任务(Task)并不等同于线程(Thread)。任务会被排队在任务调度器中,然后在一个线程上执行,但是排队100个任务并不意味着你将有100个线程在运行。

通常情况下,任务将在线程池中的一个线程上运行,线程池的大小是有限的。一旦所有这些线程都忙碌了,那么你的任务就必须等待一个线程变得可用以执行你的任务。也可以使用适当的任务调度器将它们排队到像UI线程这样的东西上,在这种情况下,它们最终会被UI线程的消息循环同步运行(当你与UI交互时,这非常有用)。

在你的任务示例中,实际上没有必要限制启动的任务数量,因为任务已经被排队,并且必须等待直到有一个线程可用来运行它们。这可能是最好的方法,因为你让系统根据系统的处理能力来确定最大线程数,而不是假设你有足够的CPU/内存来处理它。

而使用线程的示例则明确表示你将分配给它们的线程数。


“任务被排队在任务计划程序中,然后在线程上执行。”也可能不是这样。它可能根本不会在另一个线程上执行。任务基本上只是说,“有一些任务将在以后完成。完成时我会告诉你,并告诉你结果(如果有的话)。 - Servy
@Servy 我说的是“在一个线程上执行”,而不是另一个线程。它总是在一个线程上执行,无论是UI线程、任务池线程还是其他线程。 - CodingGorilla
不对。你假设任务是使用委托开始的,但并非总是如此。想象一下使用 TaskCompletionSource 创建的任务。从技术上讲,它根本没有任何要执行的内容。 - Servy
@Servy 我认为你过于字面理解了,代码必须在某个线程上执行。如果没有要执行的代码,那么我认为显然任何地方都不会发生任何事情。我的答案旨在概述运行任务和运行线程之间的区别,而不是深入讲解TPL的每个细微差别。 - CodingGorilla
你的回答并没有真正放在问题的背景下。如果你说了类似于“在这种情况下”或“在你的例子中”之类的话,那么当然可以。相反,你对Task做出了几个广泛的陈述(我断言这些陈述不是真实的),然后将这些陈述应用到这个具体的问题上(这是一个好方法,只要你的断言是正确的)。无论如何,你说我错了,而你的陈述在全球范围内是正确的,并且在这个问题的背景下也是正确的??? - Servy
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接