'Visual Studio\Debug\Windows\Tasks\Parent - empty values

It says here (docs.microsoft.com):

Parent: The ID of the task that created this task. If this is blank, the task has no parent. This is only applicable for managed programs.

It says here (docs.microsoft.com):

A child task (or nested task) is a System.Threading.Tasks.Task instance that is created in the user delegate of another task, which is known as the parent task.

I made a simple example After a few seconds, I pause and see that all the values in the parent field are empty.

Question: What code do I need to write to see the filled parent fields in the specified debug window?

using System;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            void MyMethod()
            {
                Console.WriteLine(Task.CurrentId);
                Task.Run(MyMethod);
                Thread.Sleep(10000);
            }

            MyMethod();
        }
    }
}

or

using System;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            void MyMethod()
            {
                Console.WriteLine(Task.CurrentId);
                new Task(MyMethod, TaskCreationOptions.AttachedToParent).Start();
                Thread.Sleep(10000);
            }

            MyMethod();
        }
    }
}

enter image description here



Solution 1:[1]

Disclaimer: I'm working on a MacBook so I'm using Visual Studio 2022 for Mac Preview, which does not support Tasks window (yet). So, I couldn't test my sample application


As I said in the comments section the you should explicitly define parent-child relationship between tasks otherwise they are just nested tasks.

  • In the former case if one of the child tasks fails then the parent will fail as well
  • In the latter case if one of the inner tasks fails then the outer will not fail

Rather than creating an infinite recursion I would suggest to create a sample application which simulates Task.WhenAll

var rnd = new Random();
var ids = Enumerable.Range(0, 4);

var whenAll = Task.Factory.StartNew(() =>
{
    foreach (var id in ids)
    {
        var child = Task.Factory.StartNew((taskId) =>
        {
            Console.WriteLine($"Child task '{taskId}' is started");
            Thread.Sleep(5000 + rnd.Next() % 1000);
            Console.WriteLine($"Child task '{taskId}' is finished");
        }, id, TaskCreationOptions.AttachedToParent);
    }
});

await whenAll;
Console.WriteLine("Finished");

One possible output:

Child task '0' is started
Child task '1' is started
Child task '3' is started
Child task '1' is finished
Child task '3' is finished
Child task '0' is finished
Child task '2' is finished
Finished

Task.Factory.Start instead of Task.Run

Please note that I've used Task.Factory.Start to create the parent task. The reason for this is because the Task.Run is equivalent of this:

Task.Factory.StartNew(action, 
    CancellationToken.None, 
    TaskCreationOptions.DenyChildAttach, 
    TaskScheduler.Default);

As you can see it explicitly defines that it denies attaching child tasks. So, it will not wait to its child tasks to finish.

In case of Task.Run there is no overload which anticipates TaskCreationOptions parameter.

Debugging

Pause the application after all child tasks have reported that they are started. At this point the Parent column should be populated on the child task rows with the parent's task id.


UPDATE #1: I/O bound example

In the above example the child tasks were CPU-bound. Here is an example for I/O bound.

var client = new HttpClient();
var urls = new[]
{
    "https://httpstat.us/200?sleep=5000",
    "https://httpstat.us/200?sleep=4000",
    "https://httpstat.us/200?sleep=4500",
    "https://httpstat.us/200?sleep=3000",
};

async Task Get(string url)
{
    Console.WriteLine($"Request to '{url}'");
    await client.GetAsync(url);
    Console.WriteLine($"Response from '{url}'");
}; 

var whenAll = Task.Factory.StartNew(() =>
{
    foreach (var url in urls)
    {
        var child = Task.Factory.StartNew(
              (requestUrl) => Get((string)requestUrl), 
              url,
              TaskCreationOptions.AttachedToParent)
            .Unwrap();
    }
});

await whenAll;
Console.WriteLine("Finished");
  • Since StartNew does not have overload which can accept a Func<Task> that's why here the StartNew returns a Task<Task>
  • In order to flatten / unfold that we need to call Unwrap

One possible output

Request to 'https://httpstat.us/200?sleep=5000'
Request to 'https://httpstat.us/200?sleep=3000'
Request to 'https://httpstat.us/200?sleep=4000'
Request to 'https://httpstat.us/200?sleep=4500'
Response from 'https://httpstat.us/200?sleep=3000'
Response from 'https://httpstat.us/200?sleep=4000'
Response from 'https://httpstat.us/200?sleep=4500'
Response from 'https://httpstat.us/200?sleep=5000'
Finished

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1