Waiting for a task
We can see above, that the main thread doesn’t wait for the task to complete. The task may not even start when main thread is done. The main thread with ID:20 exits before the Task thread ID:24 gets to work.

The application doesn’t wait for the Task to complete because the thread which runs the Task is a background thread. To wait on a thread we use the Wait() method on the Task object.

There are overloads available in Task.Factory.StartNew() and “new Task()” constructor that take in a parameter of type object. Let’s see an example. In the previous example the Task slept for a fixed 5 seconds. Let us change it so that the Main thread can pass in the seconds to sleep.

This is a major difference between using ThreadPool’s QueueUserWorkItem and Task library. The QueueUserWorkItem gave back a bool value which was really of no use. But in TPL we have a Task object which represents a running operation, so we can use it to return a value as well.

We have to use the generic Task<T> when we want to create a task that returns a value. The type of return value is determined by the <T> parameter. Let’s see an example.

Changes:
- used the generic Task<int> in the task definition and StartNew method.
- added a return statement in the task body which returns an integer
- used Task.Result to get the return value

You may have noticed that we didn’t use “t.Wait” anymore to wait on the Task. This is because when the Result property is accessed then there is an implicit wait called on the task. If the task had been completed by the time we access the Result property, then it returns immediately otherwise it would wait until the task is finished.

Doing something after the task finishesIn the code samples above, we are running the task and then immediately waiting for it - which is not really useful. But if you think about UI applications (WPF or WinForms), then you do not need to wait on the task. The event handler or the UI command (in WPF) which starts the task can immediately return allowing the UI to be responsive and later when the task finishes it can go ahead and update the UI.

To specify a block of code that we want to run after a task finishes – or to say create a continuation task, we use “ContinueWith”.

The ContinueWith method accepts an Action<Task> delegate. The parameter passed is of type Task and it references the task that ran before. Important concept is that ContinueWith returns a new Task. So using this we can create a chain of tasks each running one after another.

You may notice that both the task and its continuation task run on the same thread [ID:12]. But this is not always the case, the continuation task can be run on any thread. We can illustrate this by adding a Sleep in the Main method after creating the first task.

The only difference in previous two code snippets is the Thread.Sleep(10) introduced in the last one. Notice that the thread ID of task is now [15] and that of continuation is [12]. So it is not guaranteed, the TPL knows the best and decides which thread to use to run the continuation.

However, if you want to force that the continuation run on the same thread you can use the TaskContinuationOptions.ExecuteSynchronously when creating the continuation task. For e.g.:

That is all good, but what if I create the continuation task after the antecedent task has already completed? Do I have to create the continuation task in the next line itself, after creating the antecedent task? Isn’t that brittle?

Good questions, let’s see what happens if we attach the continuation task after the task has finished.

In the code sample above, we attach a continuation task after the antecedent task “RanToCompletion”. Did you notice the thread ID of the continuation task in the output? It is the same as the Main thread. It means that a new Task wasn’t created to run the continuation task, the Main (or UI) thread ran it synchronously.

Coming soon...- Updating UI from continuation task so that cross-thread access exceptions are not raised- Exceptions in tasks