This is the continuation of my previous article on Tasks, where we discussed the basic understanding of Tasks and their usage. Continuing with that, here we will discuss some more functionality and behavior of Task.

How to wait for Tasks to complete?

As discussed in my earlier blog, Task run asynchronously on a thread from the Thread Pool. The thread starts task execution asynchronously along with the main application thread as soon as it has been invoked.

We also see an example in our previous discussion on Task, where both threads (Main and Child) started their execution simultaneously (asynchronously), the Main thread didn’t wait for the Child thread to complete. The Child thread continued its execution even after the completion of Main thread.

In cases, where you want your main application thread execution to wait until other child Tasks complete its execution, then you can do that in Task by using the Wait() method.

Let’s check this concept through an example.

Example: 1 – Create a Task and wait for the Task to complete

In below example, we have created a method PrintOddEvenNumbers() that prints odd and even numbers. In the Main method, we have created a task instance Task t = new Task(PrintOddEvenNumbers); and then invoked it by calling the t.start()

In the above example, we have used t.Wait() method for Task t object. Which will make the Main thread to wait until the Child thread finishes its execution.

When we run the above program, we will get below output.

Tasks Out Put 1.1
Tasks Out Put 1.1

If you notice in the above output console, here the Main thread waits for the Child thread to finish its execution.

Get return value from Tasks?

There are scenarios when you need to return a value from a task, in that case you can use the Task<T> class, where T is the type of the value you want to return. This allows you to run asynchronous operations and retrieve results after the task completes.

Example: 2 – Return a value from Task using Task<T>

In below example, we have created a method as SumOfAllNumber that takes an integer array as input and calculates the sum of values present in the array, finally returns the calculated sum value.

  • In the Main method, we have created a task instance Task<int> task = new Task<int>(() => SumOfAllNumber(arrNum)) that has the return type as Task<int> 

When we run above example then will get below output.

Tasks Out Put 1.2
Tasks Out Put 1.2

If you look at the output, here both Main method and Task executed on different thread. However, since we are using Task.Result property in the Main method to read the return value from the Task. So, that’s why the child thread blocked the Main method until it finished execution.

Example: 3 – Return a value from using Task.FromResult()

There could be scenarios where you want to return a value from a task immediately even without performing the asynchronous work, in that case you can use Task.FromResult

Let’s take below example.

In this above example, Task.FromResult is used to return data based on a conditional check. If the condition is met, the data is returned immediately.

Task_FormResult_Out_Put_1.3
Task_FormResult_Out_Put_1.3

Example: 4 – Returning a Complex Types from Task using Task<T>

You can also return complex types, such as custom objects or collections, from a task by passing the object type in the type T parameter.

When we run above program, we will get output as below.

Task_FormResult_Complex_Type_Out_Put_4.0
Task_FormResult_Complex_Type_Out_Put_4.0

In this case, GetStudentDetailAsync() method returns a Student object after having a delay to simulate some work. You can use this approach to return any complex type from a task.

Conclusion:

In C#, you can easily get a return value from a task using the Task&lt;T&gt; type. The await keyword is the common way to retrieve the result of an asynchronous operation without blocking the main thread. Thanks for your time 🙂 Please provide your feedback and keep visit for more blogs.