Building your own task scheduler in C#

Take advantage of a custom task scheduler to provide added functionalities over the default task scheduler and manage how tasks are scheduled in .Net

Custom task scheduler

Custom task scheduler

The TPL (Task Parallel Library) is one of the most interesting new features in the recent versions of .Net framework, having first been introduced in .Net framework 4.0. To work with TPL you would need to take advantage of the System.Threading.Tasks namespace.

What are task schedulers? Why do we need them?

Now, how is that the tasks are scheduled? Well, there is a component called task scheduler that is responsible for scheduling your tasks. In essence, it's an abstraction for a low-level object that can queue your tasks onto threads.

The .Net framework provides you with two task schedulers. These include the default task scheduler that runs on the .Net framework thread pool, and there's another task scheduler that executes on the synchronization context of a specified target. Note that the default task scheduler of the TPL takes advantage of the .Net framework thread pool. This thread pool is in turn represented by the ThreadPool class that is contained inside the System.Threading.Tasks namespace.

Although the default task scheduler will suffice most of the time, you may want to build your own custom task scheduler to provide added functionalities, i.e. features that are not provided by the default task scheduler. Such features may include, FIFO execution, degree of concurrency, etc.

Creating a custom task scheduler

To build your own custom task scheduler you would need to create a class that extends the System.Threading.Tasks.TaskScheduler class. So, to build a custom task scheduler, you would need to extend the TaskScheduler abstract class and override the following methods.

  • QueueTask returns void and accepts a Task object as parameter and this method is called when a task is to be scheduled
  • GetScheduledTasks  returns a list (an IEnumerable to be precise) of all the tasks that have been scheduled
  • TryExecuteTaskInline is used to execute tasks inline, i.e., on the current thread. In this case, the tasks are executed sans the need of queuing them

The following code snippet shows how you can extend the TaskScheduler class to implement your custom scheduler in C#.

public class CustomTaskScheduler : TaskScheduler, IDisposable

    {

    }

As we discussed earlier in this article, you would need to override the GetScheduledTasks, QueueTask and TryExecuteTaskInline methods in the custom task scheduler.

public sealed class CustomTaskScheduler : TaskScheduler, IDisposable

  {

        protected override IEnumerable<Task> GetScheduledTasks()

        {

            //TODO

        }

        protected override void QueueTask(Task task)

        {

             //TODO

        }

        protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)

        {

            //TODO

        }

        public void Dispose()

        {

            //TODO

        }

  }

Let's now start implementing our custom task scheduler. The following code snippet shows how you can leverage BlockingCollection to store a collection of task objects.

public sealed class CustomTaskScheduler : TaskScheduler, IDisposable

 {

        private BlockingCollection<Task> tasksCollection = new BlockingCollection<Task>();

        private readonly Thread mainThread = null;

        public CustomTaskScheduler()

        {

            mainThread = new Thread(new ThreadStart(Execute));

            if (!mainThread.IsAlive)

            {

                mainThread.Start();

            }

        }

        private void Execute()

        {

            foreach (var task in tasksCollection.GetConsumingEnumerable())

            {

                TryExecuteTask(task);

            }

        } 

      //Other methods

  }

Refer to the constructor of the CustomTaskScheduler class. Note how a new thread has been created and started to run the Execute method.

Next, we would need to implement the three methods that we need to override in our custom task scheduler. These three methods include the GetScheduledTasks, QueueTask and TryExecuteTaskInline. The GetScheduledTasks method returns the instance of the task collection as IEnumerable. This is used so that you can enumerate the collection as shown in the Execute method. The QueueTask method accepts a Task object as a parameter and stores in in the task collection. The TryExecuteTaskInline method doesn't have an implementation -- I leave it to the reader to implement it.

protected override IEnumerable<Task> GetScheduledTasks()

        {

            return tasksCollection.ToArray();

        }

        protected override void QueueTask(Task task)

        {

            if (task != null)

                tasksCollection.Add(task);

        }

        protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)

        {

            return false;

        }

The following code listing illustrates how the final version CustomTaskScheduler now looks like.

 public sealed class CustomTaskScheduler : TaskScheduler, IDisposable

    {

        private BlockingCollection<Task> tasksCollection = new BlockingCollection<Task>();

        private readonly Thread mainThread = null;

        public CustomTaskScheduler()

        {

            mainThread = new Thread(new ThreadStart(Execute));

            if (!mainThread.IsAlive)

            {

                mainThread.Start();

            }

        }

        private void Execute()

        {

            foreach (var task in tasksCollection.GetConsumingEnumerable())

            {

                TryExecuteTask(task);

            }

        }

        protected override IEnumerable<Task> GetScheduledTasks()

        {

            return tasksCollection.ToArray();

        }

        protected override void QueueTask(Task task)

        {

            if (task != null)

                tasksCollection.Add(task);           

        }

        protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)

        {

            return false;

        }

        private void Dispose(bool disposing)

        {

            if (!disposing) return;

            tasksCollection.CompleteAdding();

            tasksCollection.Dispose();

        }

        public void Dispose()

        {

            Dispose(true);

            GC.SuppressFinalize(this);

        }

    }

To use the custom task scheduler we just implemented, you can use the following code snippet:

CustomTaskScheduler taskScheduler = new CustomTaskScheduler();

Task.Factory.StartNew(() => SomeMethod(), CancellationToken.None, TaskCreationOptions.None, taskScheduler);

This article is published as part of the IDG Contributor Network. Want to Join?

To comment on this article and other InfoWorld content, visit InfoWorld's LinkedIn page, Facebook page and Twitter stream.
From CIO: 8 Free Online Courses to Grow Your Tech Skills
Notice to our Readers
We're now using social media to take your comments and feedback. Learn more about this here.