为什么TaskFactory.StartNew任务没有立即启动?

时间:2012-04-22 07:58:09

标签: .net c#-4.0 .net-4.0 asynchronous

根据MSDN Documentation TaskFactory.StartNew创建并启动任务。所以,对于下面的代码示例

class Program
{
    public static void Main()
    {
        var t =Task.Factory.StartNew(
                () => SomeLongRunningCalculation(10, Display)
            );
        var t1 = Task.Factory.StartNew(
                () => SomeLongRunningCalculation(20, Display)
            );
        Console.WriteLine("Invoked tasks");
        Task.WaitAll(t, t1);
        Console.ReadLine();
    }

    public static void Display(int value)
    {
        Console.WriteLine(value);
    }

    public static void SomeLongRunningCalculation(int j, Action<int> callBack)
    {
        Console.WriteLine("Invoking calculation for {0}", j);
        System.Threading.Thread.Sleep(1000);
        if (callBack != null)
        {
            callBack(j + 1);
        }
    }
}      

我的预期输出是

Invoking calculation for 10
Invoking calculation for 20
Invoked tasks
11 
21

但是,它正在显示

Invoked tasks
Invoking calculation for 20
Invoking calculation for 10
21
11

我想学习

  1. 为什么在StartNew之后任务没有立即运行?
  2. 我该怎么办,以预期的格式获得输出?

3 个答案:

答案 0 :(得分:11)

这在具有单核cpu的计算机上非常可能产生结果。或者可能在具有多核cpu的一个上,并且它也忙于做其他事情。

创建任务或线程只会设置允许代码运行的逻辑操作系统结构。如果内核繁忙,操作系统调度程序立即开始执行它,该线程必须与机器上运行的所有其他线程竞争。典型的Windows会话有一千个左右。每秒64次,内核生成一个中断,调度程序重新评估正在发生的事情,看看另一个线程是否应该转向。任何未阻塞的线程(等待某些其他线程完成工作,如读取文件或网络数据包)都有资格运行,调度程序选择具有最高优先级的线程。调度程序中的一些额外代码具有优先级值,以确保所有线程都有机会。

机会是关键词。线程调度非确定性

请注意我从未说过有关Thread或Task或ThreadPool类的任何内容。他们无权对操作系统调度线程的方式做很多事情。所有可能的是阻止线程运行,线程池调度程序的工作。

优先级很重要,您可以修改Thread.Priority或Task.Priority属性以影响结果。但是没有灌篮,操作系统调度程序会根据您使用该属性设置的基本优先级不断调整线程优先级。例如,您无法通过另一个具有更高优先级的线程来阻止永远运行的线程。

希望线程以可预测的顺序运行会导致最糟糕的bug,即线程争用错误。第二个最糟糕的是死锁。它们非常难以调试,因为它们依赖于时序和可用的机器资源和负载。您只能通过编写明确处理它的代码来确保您获得特定订单。您可以使用Mutex等线程原语或 lock 关键字来执行此操作。值得注意的是,当您尝试将此类代码添加到您的代码段时,您最终会得到一个不再具有并发性的程序。换句话说,你最终将不再使用任务了。只有当 能够让它在不可预测的时间运行时,线程或任务才会有用。

答案 1 :(得分:7)

  

为什么在StartNew之后任务没有立即运行?

关于MSDN StartNew()安排执行任务。

  

调用StartNew在功能上等同于使用创建任务   它的一个构造函数,然后调用Start来安排它   执行。但是,除非创建和调度必须分开,   StartNew是简单和简单的推荐方法   性能

由于TPL使用ThreadPool线程 - 有时它必须做一些工作才能为特定任务执行保留和启动ThreadPool线程。如果你需要显式运行一个单独的线程而没有任何中间机制,比如TPL的TaskScheduler - 手动创建并启动一个线程,并且显然你不会有像continuation这样的整洁东西。

答案 2 :(得分:0)

  

请注意我从未说过有关Thread或Task或ThreadPool的任何内容   类。他们无权对这条路做任何事情   操作系统调度线程。所有可能的都是预防   来自运行的线程,线程池调度程序的作业。

战争......任务与线程......这取决于所需的任务。

对于样本,我们需要从互联网上加载100张图片,同时执行100个任务(一个客户端一个任务),为100个客户端制作地图图块(一个客户端使用一个图块)。我们有一个共同的时间限制,一些加载任务的加载时间可能会重叠共同的时间限制。 简单的测试表明,与执行100个任务(类任务)相比,在有限时间内同时执行100个线程(类线程),效率更高。 10个线程与10个任务的结果相同。 我的意思是,如果我们需要的不仅仅是“一些缓慢的”,而是强大的任务,即在同时执行的任务中做更多的工作,那么我们应该使用类Thread。