9

根据MSDN 文档 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. 我应该怎么做才能获得预期格式的输出?
4

3 回答 3

12

这在具有单核 CPU 的机器上很可能出现。或者可能在一个多核 cpu 上,它也忙于做其他事情。

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

机会是这里的关键词。线程调度是不确定的

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

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

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

于 2012-04-22T09:35:24.880 回答
7

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

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

调用 StartNew 在功能上等同于使用其构造函数之一创建一个 Task,然后调用 Start 来安排它的执行。但是,除非必须将创建和调度分开,否则出于简单性和性能考虑,建议使用 StartNew 方法。

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

于 2012-04-22T08:02:55.377 回答
0

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

……任务与线程之战……这取决于所需的任务。

对于示例,我们需要从 Internet 加载 100 张图片,同时执行 100 个任务(一个客户端一个任务)来为 100 个客户端制作地图瓦片(一个客户端一个瓦片)。并且我们有一个共同的时间限制,一些加载任务的加载时间可以与共同的时间限制重叠。简单的测试表明,在有限的时间内同时执行 100 个线程(线程类)比执行 100 个任务(任务类)效率高得多。10 个线程与 10 个任务的结果相同。我的意思是,如果我们需要的不仅仅是“一些缓慢的”但稳健的任务,即在同时执行的任务中做更多的工作,那么我们应该使用 Thread 类。

于 2017-10-24T11:43:27.747 回答