If you view a
Task as a state machine, then the
Status property represents the current state. Different types of tasks take different paths through the state machine.
As usual, this post is just taking what Stephen Toub already said, expounding on it a bit, and drawing some ugly pictures. :)
Delegate Tasks follow the basic pattern in the image below:
Usually, Delegate Tasks are created via
Task.Factory.StartNew), and enter the state machine at the
WaitingToRun means that the task is associated with a task scheduler, and is just waiting its turn to run.
Entering at the
WaitingToRun state is the normal path for Delegate Tasks, but there are a couple of other possibilities.
If a Delegate Task is started with the task constructor, then it starts in the
Created state and only moves to the
WaitingToRun state when you assign it to a task scheduler via
If a Delegate Task is a continuation of another task, then it starts in the
WaitingForActivation state and automatically moves to the
WaitingToRun state when that other task completes.
The task is in the
Running state when the delegate of the Delegate Task is actually executing. When it’s done, the task proceeds to the
WaitingForChildrenToComplete state until its children are all completed. Finally, the task ends up in one of the three final states:
Remember that since Delegate Tasks represent running code, it’s quite possible that you may not see one or more of these states. For example, it’s possible to queue some very fast work to the thread pool and have that task already completed by the time it’s returned to your code.
Also, this state machine can be short-circuited at any point if the task is canceled. It is possible for the task to be canceled before it enters the
Running state, and thus not even execute its delegate.
Promise Tasks have a much simpler state machine:
This diagram is slightly simplified; technically, Promise Tasks can enter the
WaitingForChildrenToComplete state. However, this is rather non-intuitive and for this reason tasks created for
async use usually specify the
It is natural to speak of I/O-based operations as “running” or “executing”, e.g., “the HTTP download is currently running”. However, there is no actual CPU code to be run, so Promise Tasks (such as an HTTP download task) will never enter the
Running states. And yes, this means that a Promise Task may end in the
RanToCompletion state without ever actually running. Well, it is what it is…
All Promise Tasks are created “hot”, meaning that the operation is in progress. The confusing part is that this “in-progress” state is actually called
For this reason, I try to avoid using the terms “running” or “executing” when talking about Promise Tasks; instead, I prefer to say that “the operation is in progress”.
Task has a few convenience properties for determining the final state of a task:
IsFaulted map directly to the
Faulted states, but
IsCompleted is tricky.
IsCompleted does not map to
RanToCompletion; rather, it is
true if the task is in any final state. In other words:
As interesting as these state properties all are, they are hardly ever actually used (except for debugging). Both asynchronous and parallel code do not normally use
Status or the three convenience properties; instead, the normal usage is to wait for the tasks to complete and extract the results.