c# - 继续任务上的 Task.WaitAll() 只会延迟原始任务的执行?

标签 c# .net 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());
    }
}

最佳答案

我认为您看到了 Task Inlining 行为。引自 MSDN :

In some cases, when a Task is waited on, it may be executed synchronously on the Thread that is performing the wait operation. This enhances performance, as it prevents the need for an additional Thread by utilizing the existing Thread which would have blocked, otherwise. To prevent errors due to re-entrancy, task inlining only occurs when the wait target is found in the relevant Thread's local queue.

您不需要 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
    }
}

在您的原始程序中,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());
}

这是示例输出 - 关于注释 tasks.Add(originalTask​​); - 这是你的第一个 block 。

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

还有一些示例输出 - 关于保持 tasks.Add(originalTask​​); 这是你的第二个 block

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

正如您在第二种情况下看到的那样,当您在启动它的同一线程上等待 originalTask​​ 时,任务内联 将使它在同一线程上运行 - 这是为什么您之前看到 Got Number.. 消息。

关于c# - 继续任务上的 Task.WaitAll() 只会延迟原始任务的执行?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22024879/

相关文章:

c# - Protobuf-net 似乎是对值类型进行包装。事实是这样吗?

c# 将文件从源文件夹复制到目标文件夹

c# - 如何使用ArrayList的 "CopyTo"方法到另一个ArrayList对象?

c# - 在 C# 中从已运行的任务中初始化新任务

java - 多线程降低了 NUMA 上的套接字吞吐量

c# - 计时器线程是等到回调函数中的所有步骤都完成还是回调函数在每个周期都被重新调用

c# - 编写 xml 并将其读回 c#

c# - 通过 C# 进行 Apache spark 查询

.net - 操作系统版本先决条件

javascript - 未捕获的语法错误 : Unexpected token : while trying to create url path