The msdn API reference defines the Task class as a representation of an asynchronous operation. However, as soon as you start writing Task based code for asynchronous/parallel programming, or simply offloading work to a thread pool thread, it becomes obvious that Task has more than one face based on the use case.

Task Faces


Here the Task class takes a delegate to manage and run. The delegate can be either an Action (Task) or a Func (Task<T>). The delegate will travel through the task life cycle journey; WaitingToRun, Running, Cancelled/Faulted/RanToCompletion. In the code example above, the first task takes a method that returns an int, and this method ultimately will get executed by a CLR thread pool thread; other than the caller thread. Since the method is wrapped by a task, the task object becomes the entry point to interact with the method e.g. task.Wait(), task.IsFaulted, etc.

Some times is helpful to think of the active task as delegate task, action task, pool task, offloading task, or work based task.


Here you don’t create a Task, instead the awaited method passes (returns) a Task/Task<T> to the caller. When writing asynchronous code the await keyword is expecting the called method to return a Task that promises to notify the caller when it completes at some point in the future, so the code can resume executing at the point the caller started awaiting. In other words the called method passes information to the caller method.

Some times is helpful to think of the passive task as future task, promise task, passed task, or async based task.

Where it Matters?

The different faces of Task class is kind of different mindsets. Since the way you think about a problem really counts, understanding that a task has two faces (use cases) improves they way you read and write code and ultimately makes you handle task based programming more efficiently.

Method Return Signature

First thing you notice is that an awaited asynchronous method  must return a Task/Task<T>, but the method body doesn’t return a Task !? Because, simply put, the return Task is needed by the implementation of await in the framework to get notified of a future completion.

The following code example demonstrates how subtle syntax tweaks affect code behaviour.


Generally speaking exceptions in task based programming are thrown when you decide to wait a task. Because active and passive tasks are awaited differently lets have a look at the following code examples:

In the above active task when the code inside a task throws an exception you interact with its status via some status properties like IsFaulted, but exception is only thrown at the caller’s context only when it is awaited.

Passive tasks or async tasks, exceptions are thrown at the calling point of your code execution as in the code below.