Timers and the Task Parallel Library TaskCompletionSource

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

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.

Demonstration

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);
 
            tcsCancelled.SetCanceled();
 

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");
            }).StartNewTimer(timeout);
 
            try
            { tcsException.Task.Wait(); }
            catch
            { 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;
            }).StartNewTimer(timeout);
 
            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);
 

Summary

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.

Sources

Mechanisms
for Creating Tasks

 

Comparing the
Timer Classes in the .NET Framework Class Library

More by Author

Must Read