A Tour of Task, Part 8: Starting
• CommentsWhen a task is created using its constructor, it is initially in a Created
state. This is a kind of “holding” state, where the task won’t do anything until it is started. Today’s post considers ways to start tasks that have already been created.
There is absolutely nothing in this blog post that is recommended for modern code. If you’re looking for best practices, move along; there’s nothing to see here.
Start
The most basic way to start a task is to just call its Start
method. Sounds simple, right?
As you may guess by now, the default TaskScheduler
is not TaskScheduler.Default
. It is TaskScheduler.Current
. And, once again, this brings up all the same problems that Task.Factory.StartNew
has with its similar treatment of TaskScheduler
.
Originally, Start
(and the task constructors) were intended so that developers could define tasks to be executed kind of like a fancy delegate. Then, other code could execute those tasks in whatever way it felt appropriate (e.g., on the UI thread, or on a background thread). But in the real world, this is almost never useful, because what the delegate does usually determines its required context (e.g., a delegate accessing UI elements must be run on the UI thread). So this separation doesn’t make sense for most code, and even when it does, developers just use delegates directly instead of tasks.
Start
can only be called on a task that is created with the task constructor; that is, it only works on Delegate Tasks that are in the Created
state. Once Start
is called, the task moves to the WaitingToRun
state (and never returns to the Created
state), so Start
cannot be called on a task more than once. Start
cannot be called on Promise Tasks at all, since they are never in the Created
state.
For more information on the task states, Delegate Tasks, and Promise Tasks, see Part 3 (Status) of this series.
In modern code - even dynamic task parallel code - Start
just doesn’t have a place anymore. Instead of the task constructor and Start
, use Task.Run
, which creates and schedules a task.
RunSynchronously
RunSynchronously
is very similar to Start
, and has the same overloads:
RunSynchronously
will attempt to start the task immediately and execute it on the current thread. This does not always happen, however; the final decision is up to the task scheduler that is passed to RunSynchronously
. For example, a task scheduler for the UI thread will not permit a task to run on a thread pool thread. If the task scheduler refuses to execute the task synchronously, then RunSynchronously
behaves just like Start
; that is, the task is queued to the task scheduler for future execution. Also just like Start
, RunSynchronously
can only be called on a task that is in the Created
state, and can only be called on a task once.
Once again, the default TaskSchedler
is TaskScheduler.Current
. However, this time this behavior does make sense: since RunSynchronously
will attempt to execute the task’s delegate on the current thread, it is reasonable to assume the current task scheduler is the correct one to use.
Similarly to Start
, RunSynchronously
doesn’t have any place in modern code.
IAsyncResult.CompletedSynchronously
Task
has an explicit interface implementation of a member called CompletedSynchronously
:
If you believe the MSDN documentation, this member should return true
if the task was completed synchronously. Unfortunately, this member actually always returns false
, even for synchronously-completed Promise Tasks such as those returned from Task.FromResult
.
IAsyncResult.CompletedSynchronously
is used by some legacy IAsyncResult
-based code. But generally speaking, this member shouldn’t be used in modern code. In particular, you can’t depend on it being anything other than false
for tasks.