A Tour of Task, Part 3: Status
• CommentsStatus
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
Delegate Tasks follow the basic pattern in the image below:
Usually, Delegate Tasks are created via Task.Run
(or Task.Factory.StartNew
), and enter the state machine at the WaitingToRun
state. 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 Start
or RunSynchronously
.
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: RanToCompletion
(successfully), Faulted
, or Canceled
.
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
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 DenyChildAttach
flag.
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 WaitingToRun
or 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 WaitingForActivation
.
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”.
Status Properties
Task
has a few convenience properties for determining the final state of a task:
IsCanceled
and IsFaulted
map directly to the Canceled
and 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:
Status | IsCompleted |
IsCanceled |
IsFaulted |
---|---|---|---|
other | |||
RanToCompletion |
|||
Canceled |
|||
Faulted |
Conclusion
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.