关于延续任务的Task.WaitAll()只会延迟原始任务的执行?

Joa*_*rks 5 .net c# multithreading task-parallel-library

背景:

我有一个控制台应用程序,Tasks用于处理来自数据库的数据(让我们称之为Level1任务).每个任务都会再次创建自己的任务,以处理分配给它的每个数据部分(Level2任务).

每个Level2任务都有一个与之关联的延续任务,以及用于WaitAll在继续执行任务之前执行的代码.

我在.NET 4.0(不async/ await)

问题:

这产生了一个问题 - 事实证明,如果以这种方式完成,则在调度所有可用的Level1任务之前,没有启动任何Level2任务.这在任何方面都不是最优的.

题:

这似乎是通过更改代码来等待原始Level2任务及其继续任务来解决的.但是,我不完全确定为什么会这样.

你有什么想法?

我唯一能想到的是 - 由于延续任务还没有开始,所以没有必要等待它完成.但即使是这种情况,我也希望至少有一些Level2任务已经启动.他们从未做过.

例:

我创建了一个示例控制台应用程序,它正好演示了

  1. 按原样运行它,您将看到它首先安排所有任务,然后才开始从Level2任务中获取实际行.

  2. 但是注释掉标记的代码块并取消注释替换,并且所有工作都按预期工作.

你能告诉我为什么吗?

public class Program
{
    static void Main(string[] args)
    {
        for (var i = 0; i < 100; i++)
        {
            Task.Factory.StartNew(() => SomeMethod());
            //Thread.Sleep(1000);
        }

        Console.ReadLine();
    }

    private static void SomeMethod()
    {
        var numbers = new List<int>();

        for (var i = 0; i < 10; i++)
        {
            numbers.Add(i);
        }

        var tasks = new List<Task>();

        foreach (var number in numbers)
        {
            Console.WriteLine("Before start task");

            var numberSafe = number;

            /* Code to be replaced START */

            var nextTask = Task.Factory.StartNew(() =>
            {
                Console.WriteLine("Got number: {0}", numberSafe);
            })
                .ContinueWith(task =>
                {
                    Console.WriteLine("Continuation {0}", task.Id);
                });

            tasks.Add(nextTask);

            /* Code to be replaced END */

            /* Replacement START */

            //var originalTask = Task.Factory.StartNew(() =>
            //{
            //    Console.WriteLine("Got number: {0}", numberSafe);
            //});

            //var contTask = originalTask
            //    .ContinueWith(task =>
            //    {
            //        Console.WriteLine("Continuation {0}", task.Id);
            //    });

            //tasks.Add(originalTask);
            //tasks.Add(contTask);

            /* Replacement END */
        }

        Task.WaitAll(tasks.ToArray());
    }
}
Run Code Online (Sandbox Code Playgroud)

YK1*_*YK1 4

我想你正在看到这种Task Inlining行为。引用自MSDN

在某些情况下,当等待任务时,它可能会在执行等待操作的线程上同步执行。这提高了性能,因为它通过利用现有线程来防止需要额外的线程,否则该线程会被阻塞。为了防止由于重入而导致的错误,仅当在相关线程的本地队列中找到等待目标时才会发生任务内联。

你不需要 100 个任务就能看到这一点。我已将您的程序修改为具有 4 个 1 级任务(我有四核 CPU)。每个 1 级任务仅创建一个 2 级任务。

static void Main(string[] args)
{
    for (var i = 0; i < 4; i++)
    {
        int j = i;
        Task.Factory.StartNew(() => SomeMethod(j)); // j as level number
    }
}
Run Code Online (Sandbox Code Playgroud)

在你的原始程序中,这nextTask是延续任务 - 所以我只是简化了方法。

private static void SomeMethod(int num)
{
    var numbers = new List<int>();

    // create only one level 2 task for representation purpose
    for (var i = 0; i < 1; i++)
    {
        numbers.Add(i);
    }

    var tasks = new List<Task>();

    foreach (var number in numbers)
    {
        Console.WriteLine("Before start task: {0} - thread {1}", num, 
                              Thread.CurrentThread.ManagedThreadId);

        var numberSafe = number;

        var originalTask = Task.Factory.StartNew(() =>
        {
            Console.WriteLine("Got number: {0} - thread {1}", num, 
                                    Thread.CurrentThread.ManagedThreadId);
        });

        var contTask = originalTask
            .ContinueWith(task =>
            {
                Console.WriteLine("Continuation {0} - thread {1}", num, 
                                    Thread.CurrentThread.ManagedThreadId);
            });

        tasks.Add(originalTask); // comment and un-comment this line to see change in behavior

        tasks.Add(contTask); // same as adding nextTask in your original prog.

    }

    Task.WaitAll(tasks.ToArray());
}
Run Code Online (Sandbox Code Playgroud)

这是示例输出 - 关于评论tasks.Add(originalTask);- 这是您的第一个块。

Before start task: 0 - thread 4
Before start task: 2 - thread 3
Before start task: 3 - thread 6
Before start task: 1 - thread 5
Got number: 0 - thread 7
Continuation 0 - thread 7
Got number: 1 - thread 7
Continuation 1 - thread 7
Got number: 3 - thread 7
Continuation 3 - thread 7
Got number: 2 - thread 4
Continuation 2 - thread 4
Run Code Online (Sandbox Code Playgroud)

以及一些示例输出 - 保留tasks.Add(originalTask);第二个块

Before start task: 0 - thread 4
Before start task: 1 - thread 6
Before start task: 2 - thread 5
Got number: 0 - thread 4
Before start task: 3 - thread 3
Got number: 3 - thread 3
Got number: 1 - thread 6
Got number: 2 - thread 5
Continuation 0 - thread 7
Continuation 1 - thread 7
Continuation 3 - thread 7
Continuation 2 - thread 4
Run Code Online (Sandbox Code Playgroud)

正如您在第二种情况下看到的,当您在originalTask启动它的同一线程上等待时,这task inlining将使其在同一线程上运行 - 这就是您Got Number..之前看到消息的原因。