1

It says here (learn.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 (learn.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

  • 1
    Why do you "test" the `Tasks` window with an infinite recursion? – Peter Csala Feb 16 '22 at 08:45
  • @PeterCsala. Why not? The Task.Run method initializes, starts and returns a task, within which the Task.Run method initializes, starts and returns a task, and so on. One task creates another. As it says in the manual. Maybe I misunderstood something. Just give the correct code as an example - any. –  Feb 16 '22 at 23:20
  • I'm using Visual Studio for Mac where this feature is still lacking so I can't be sure but I think the **Parent** will be populated whenever there is an explicit parent-child relationship between tasks. You can do that by specifying the `TaskCreationOptions` like this: `Task.Factory.StartNew(action: ..., state: ..., creationOptions: TaskCreationOptions.AttachedToParent)` – Peter Csala Feb 17 '22 at 07:45
  • @PeterCsala. Done. No. All the same . –  Feb 17 '22 at 12:42
  • Could you please amend your post to see how did you use the `TaskCreationOptions.AttachedToParent` ? – Peter Csala Feb 17 '22 at 12:52
  • @PeterCsala. Ready. –  Feb 18 '22 at 01:26
  • Please try to [avoid the direct usage of `Task` constructor](https://blog.stephencleary.com/2014/05/a-tour-of-task-part-1-constructors.html), please prefer (as I suggested) `Task.Factory.StartNew` – Peter Csala Feb 18 '22 at 13:03
  • @PeterCsala. Ready. Everything remains the same. Code: 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.Factory.StartNew(MyMethod, TaskCreationOptions.AttachedToParent); Thread.Sleep(10000); } MyMethod(); } } } –  Feb 19 '22 at 02:47
  • Hi, any update about this issue? – Jingmiao Xu-MSFT Mar 03 '22 at 07:25

1 Answers1

0

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
Peter Csala
  • 17,736
  • 16
  • 35
  • 75
  • 1
    As a side note, the `Random` class [is not thread safe](https://stackoverflow.com/questions/3049467/is-c-sharp-random-number-generator-thread-safe). One way to fix the example is this: `int delay; lock (rnd) delay = 5000 + rnd.Next() % 1000; Thread.Sleep(delay);` – Theodor Zoulias Feb 19 '22 at 09:01
  • 1
    Thanks. Sorry, but that doesn't work either. https://i.stack.imgur.com/avzA7.png –  Feb 19 '22 at 09:10
  • @NikVladi Hmm that's strange. Let me try one last thing. I've created an I/O bound version of the same app. Could you please give it a try? – Peter Csala Feb 19 '22 at 09:37
  • Thanks. Unfortunately no. https://i.stack.imgur.com/Ypw0W.png –  Feb 19 '22 at 10:53
  • @NikVladi Well that's sad... I have run out of ideas. Sorry – Peter Csala Feb 19 '22 at 13:04
  • 1
    I wrote here https://github.com/MicrosoftDocs/visualstudio-docs/issues/7696 –  Feb 19 '22 at 13:25
  • Okay, let's watch on github. And here I delete my account again in order to create a new one in a day, because the system considers that my questions are bad and I can ask them once a week =). –  Feb 20 '22 at 23:17
  • @NikVladi What do you mean by this: *my questions are bad*? – Peter Csala Feb 21 '22 at 06:01
  • @PeterCsala. When I try to create a new question, I can't do it and the system says that my questions are of poor quality and I have to wait a week. I delete the account and a day later I create a new one with the same email and now I can ask questions again. –  Feb 22 '22 at 00:35
  • @NikVladi As I can see the github issue has been closed without resolution :( In the meanwhile I've bumped into this [MSDN document](https://learn.microsoft.com/en-us/visualstudio/debugger/walkthrough-debugging-a-parallel-application?view=vs-2022#using-the-parallel-tasks-window-and-the-tasks-view-of-the-parallel-stacks-window). I've tried that demo on one of my colleague's windows machine and the `Parent` column were still empty. I suspect that this column will be only populated if you have less worker threads than children tasks. – Peter Csala Feb 24 '22 at 09:57