Timers and the Task Parallel Library TaskCompletionSource

by Jeffrey Juday

Developers typically choose a Timer to have an application perform a concurrent background process after some elapsed interval. Wouldn't it be nice to couple results and Timer control in a single class? Task Parallel Library (TPL) includes a class called TaskCompletionSource that enables this scenario.

Often an application must perform a concurrent background process after some elapsed interval. Developers typically choose a Timer for such an operation. Since .NET's inception, Timers have been part of the Framework. Strangely though, Timers haven't evolved much.

Timer mechanisms essentially work the same today as the early .NET days. A developer schedules a Timer, includes a delegate the timer executes, and away the Timer goes. Separate objects are needed to store a reference to the Timer and the results of the Timer's execution. Wouldn't it be nice to couple results and Timer control in a single class? Task Parallel Library (TPL) includes a class called TaskCompletionSource that enables this scenario.

Timers Revisited

Timers live throughout the .NET Framework. This article's solutions use the Timer in the System.Threading namespace. The sample code below demonstrates declaring and running a Timer.

            Timer timer = null;

            timer = new Timer(obj =>
                Console.WriteLine("Sample Timer ran");
            , null,new TimeSpan(0,0,1), TimeSpan.FromMilliseconds(Timeout.Infinite));

A Timer is configured to run an Action after an elapsed time period. Timers can be scheduled to run continuously on a scheduled interval or once after an interval expires. System.Threading Timers leverage the .NET Thread Pool.

Like most work items in the Thread Pool, an executing Timer should run a short workload. Difficulties can arise when a Timer on a scheduled interval executes a workload that runs longer than the scheduled interval. Timers are concurrent operations and therefore suffer the same problem as other concurrent code. So, for example, care should be exercised when dealing with shared memory.

Like any other piece of code, Timers can generate Exceptions. A developer may want to cancel a scheduled Timer or even schedule some other operation in response to a completed Timer. TPL Task semantics elegantly surface exceptions and other code execution results. Timers are not Tasks. However, that doesn't mean that Tasks are the only path to Task-like semantics.


TaskCompletionSource controls the result properties of an underlying Task<T> class. Some properties and methods on the TaskCompletionSource appear below.

    public class TaskCompletionSource<TResult>
        public TaskCompletionSource();
        public void SetCanceled();
        public void SetException(Exception exception);
        public void SetResult(TResult result);

        public bool TrySetCanceled();
        public bool TrySetException(Exception exception);
        public bool TrySetResult(TResult result);

Like many TPL classes there are two sets of methods for manipulating the class. The "Set" methods change the underlying Task's state from cancelled, faulted, or completed depending on the method. "Try" operations attempt to transition state and return a Boolean indicating the operation's success. An underlying Task in, for example, a cancelled state attempting transition to some other state will generate an Exception using "Set" operation and return "false" with the Try operations.

With a Task underpinning the TaskCompletionSource; TPL patterns like, for example, Continuations are at a developers fingertips. An introduction to Continuations is beyond the scope of this article, but can be found here: Microsoft .NET Framework 4.0 Task Parallel Library Continuations. Think of a Continuation as a callback or event that executes in response to a completed Task. Continuations can be configured to execute whenever a Task completes or, for example, when a Task generates an Exception. The sample Timer solution leverages Continuations and an Extension Method.

Extension Method Implementation

Extension methods are an easy way to extend a class without resorting to sub classing. As stated earlier, Timer implementation difficulties center on having a single place to control a Timer's execution and query a Timer's execution results. A Timer executes a method so the solution extends the Func<T> delegate. An Extension Method implementation appears below.

    static class TcsExts

{ public static TaskCompletionSource<T> StartNewTimer<T>(this Func<T> func, TimeSpan timeout) { Timer timer = null; TaskCompletionSource<T> tcs = new TaskCompletionSource<T>();   timer = new Timer(obj => { try { //Cancelled could have been called if (!(tcs.Task.IsCompleted)) { var val = func();   tcs.TrySetResult(val); } } catch (Exception ex) { tcs.TrySetException(ex); } } , null, timeout, TimeSpan.FromMilliseconds(Timeout.Infinite));   tcs.Task.ContinueWith(t => { Console.WriteLine("Dispose Continuation ran value is " + t.Status.ToString()); timer.Dispose(); } );   return tcs; } }  

As would be expected the code creates a Timer. A Continuation handles Timer disposal. The Continuation runs whenever the Task underlying a TaskCompletionSource transitions to cancelled, faulted, or completed. Disposal could have been handled inside the Timer's executing code, however, this arrangement would have ruled out cancelling the Timer. The IsCompleted check looks for situations where Dispose could not cancel the Timer in time. One downside to the Continuation is that a Continuation is a scheduled Task and therefore, must wait its turn to execute inside of TPL. The code returns a TaskCompletionSource. Now that the Timer is being controlled with Task-like semantics there are a number of avenues to employing a Timer.


The code below demonstrates executing and then cancelling the Timer.

            var tcsCancelled = new Func<object>(() =>
                Console.WriteLine("Timer Task Cancelled ran.. should not get called");
                return null;
            }).StartNewTimer( timeout);

Since cancelling happens when the Dispose method is called; cancelling does not guarantee that the Timer code will not execute.

The example below demonstrates an Exception handling scenario.

            var tcsException = new Func<object>(() =>
                Console.WriteLine("Timer Task Exception ran");
                throw new Exception("Generated exception");
            { tcsException.Task.Wait(); }
            { Console.WriteLine("tcsException threw an exception"); }

Just like any other TPL Task, the thrown Exception will trickle to code pausing on a Wait statement.

Continuations can be connected to the underlying Task object.

            var tcs = new Func<object>(() =>
                Console.WriteLine("Timer Task ran");
                return null;
            tcs.Task.ContinueWith((t) =>
                Console.WriteLine("This continuation also ran.");

One important point is multiple Continuations can be chained to a Task. Only one Continuation is demonstrated above. Remember though; an underlying Continuation is already assigned in the Extension Method.

Also, as mentioned earlier, Timers are concurrent. However, now that the Timer result is controlled by a concurrent friendly class like TaskCompletionSource a developer can worry a little less.

Finally, code is not limited to a Func<T>. Simply casting to a Method or delegate with the Func<T> signature like in the sample below will allow access to the Extension Method.

            //Can use anything matching the Func signature
            var tcsAlso = ((Func<object>)Program.FuncToRun).StartNewTimer(timeout);


Timers have not evolved much since the early .NET Framework days. Being a form of concurrent code a Timer benefits from new classes in the .NET Framework Task Parallel Library. Wrapping a Timer in a single TaskCompletionSource class serves multiple functions often implemented in two or more classes. Among the functions are Control over the Timer and a concurrent friendly way to view a Timer's status.


"Mechanisms for Creating Tasks"


"Comparing the Timer Classes in the .NET Framework Class Library"

This article was originally published on Friday Sep 2nd 2011
Mobile Site | Full Site