Working With Nested Tasks (2024)

Working With Nested Tasks (3)

In this article, we’ll delve deeper into the topic of nested tasks in .NET and their usage. Assuming you already have a basic understanding of the TPL, we’ll focus on key aspects that will help you fine-tune your skills when working with nested asynchrony.

When you introduce asynchronous behaviour in your software, you often may have tasks that create other tasks as well. These nested tasks are also called child tasks, and the tasks from which they emerge are called parent tasks. We have options to configure how these child tasks will operate, but we cannot useTask.Run to do so.

While Task.Run and Task.Factory.StartNew both create tasks, they differ in terms of flexibility and default behaviour. Task.Run is a simpler method with fewer configuration options, making it suitable for most scenarios. In contrast, Task.Factory.StartNew provides more customization options, such as TaskCreationOptions, but should only be used in more complex scenarios. Under the hood Task.Run uses Task.Factory.StartNewwith some predefined values to create the task. It's just a shorter form which is suitable for the most cases.

// Task.Factory.StartNew Overloads

StartNew(Action);
StartNew(Action, CancellationToken);
StartNew(Action, TaskCreationOptions);
StartNew(Action, CancellationToken, TaskCreationOptions, TaskScheduler);

Let's look at another case where creating a task with a Task.Run is different from Task.Factory.StartNew. For example, we pass an asynchronous Operation to the Task.Factory.StartNewMethod. If we await for the task that we created, we are not getting the result as we would assume for a task, that we created with Task.Run, instead we are getting the child task. If we want to have the result we have to await twice as the following code shows:

// Create a Task with StartNew
var task = Task.Factory.StartNew(async () => {
await Task.Delay(2000);
return "Result";
}); // Task<Task<string>>

var result1 = await task; // result1 will return the childtask Task<string>

var result2 = await await task; // string

// Create a Task with Task.Run
var task2 = Task.Run(async () => {
await Task.Delay(2000);
return "Result";
}); // Task<string>

var result3 = await task2; // string

So Task.Run not only uses Task.Factory.StartNew in the background, it also unwraps the child task for us, so we only have to await the task produced by Task.Run in order to get the result.

Nested tasks in .NET can be either “attached” or “detached”. An attached task is one where the parent task waits for the completion of the child task before it can complete itself. In contrast, a detached task has no such relationship with the parent task and executes independently.

Detached Tasks

You should use detached tasks when you want to perform independent operations without waiting for their completion. This can be useful for improving performance by running multiple tasks concurrently without blocking the progress of the parent task. If we create nested tasks with Task.Run the child tasks will be detached, as the example shows:

Console.WriteLine("Start");

await Task.Run(() => {
Task.Run(() => Console.WriteLine("First"));
Task.Run(() => Console.WriteLine("Second"));
});

Console.WriteLine("End");

will result in the behaviour that the parent task don't wait till its children completes, instead the parent could complete before its children, as the following output shows:

Start
End
First
Second

Attached Tasks

You should use attached tasks when you want to ensure that a parent task completes only after all child tasks have completed. This can be helpful when you need to break a task into smaller tasks and calculate the overall result only after the completion of all subtasks. In order to do that, we need to introduce the TaskCreationOption.AttachedToParent.

Console.WriteLine("Start");

await Task.Factory.StartNew(() => {

Task.Factory.StartNew(() => Console.WriteLine("First"),
TaskCreationOptions.AttachedToParent);

Task.Factory.StartNew(() => Console.WriteLine("Second"),
TaskCreationOptions.AttachedToParent);

});

Console.WriteLine("End");

The code will result in

Start
First
Second
End

Note that if a Parent task is configured with the DenyChildAttached option, the AttachedToParent option in the child task has no effect and the child task will execute as a detached child task. So the following code:

Console.WriteLine("Start");

await Task.Factory.StartNew(() => {

Task.Factory.StartNew(() => Console.WriteLine("First"),
TaskCreationOptions.AttachedToParent);

Task.Factory.StartNew(() => Console.WriteLine("Second"),
TaskCreationOptions.AttachedToParent);

}, TaskCreationOptions.DenyChildAttach);

Console.WriteLine("End");

will result in the same behaviour that we have at the start. Which means that none of the child task will be attached to the parent.

Start
End
First
Second

An exception thrown by a detached child task will not be automatically propagated by its parent. This means that if you do not wait for child tasks to complete, these exceptions may not be handled and could be lost altogether. The exception must be observed or handled directly in the parent task.

If an attached child task throws an exception, it is automatically propagated to the parent task and back to the thread that is waiting for or attempting to access the task’s Task<TResult>.Result property. As a result, by using attached child tasks, you can handle all exceptions at a single point in the Task.Wait on the calling thread.

As a seasoned .NET developer, understanding these concepts and choosing the right task configuration for your specific needs is crucial for creating efficient and responsive applications. By mastering the art of working with attached and detached tasks, you’ll be well-equipped to tackle more complex scenarios that comes to your way.

Happy coding, and don’t forget to keep experimenting with .NET’s powerful multithreading features!

Working With Nested Tasks (2024)

References

Top Articles
Latest Posts
Article information

Author: Tuan Roob DDS

Last Updated:

Views: 6043

Rating: 4.1 / 5 (42 voted)

Reviews: 89% of readers found this page helpful

Author information

Name: Tuan Roob DDS

Birthday: 1999-11-20

Address: Suite 592 642 Pfannerstill Island, South Keila, LA 74970-3076

Phone: +9617721773649

Job: Marketing Producer

Hobby: Skydiving, Flag Football, Knitting, Running, Lego building, Hunting, Juggling

Introduction: My name is Tuan Roob DDS, I am a friendly, good, energetic, faithful, fantastic, gentle, enchanting person who loves writing and wants to share my knowledge and understanding with you.