joydip_kanjilal
Contributor

How to build your own task scheduler in C#

how-to
Apr 29, 20165 mins
Software Development

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

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 the 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 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.

Extend the TaskScheduler class in C#

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
        }
  }

Use BlockingCollection to store a collection of task objects in C#

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.

Implement the GetScheduledTasks, QueueTask, and TryExecuteTaskInline methods in C#

Next, we 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 it in the task collection. The TryExecuteTaskInline method doesn’t have an implementation — I will 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;
        }

Complete CustomTaskScheduler example in C#

The following code listing illustrates the final version of our CustomTaskScheduler.

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);

How to do more in C#:

joydip_kanjilal
Contributor

Joydip Kanjilal is a Microsoft Most Valuable Professional (MVP) in ASP.NET, as well as a speaker and the author of several books and articles. He received the prestigious MVP award for 2007, 2008, 2009, 2010, 2011, and 2012.

He has more than 20 years of experience in IT, with more than 16 years in Microsoft .Net and related technologies. He has been selected as MSDN Featured Developer of the Fortnight (MSDN) and as Community Credit Winner several times.

He is the author of eight books and more than 500 articles. Many of his articles have been featured at Microsoft’s Official Site on ASP.Net.

He was a speaker at the Spark IT 2010 event and at the Dr. Dobb’s Conference 2014 in Bangalore. He has also worked as a judge for the Jolt Awards at Dr. Dobb's Journal. He is a regular speaker at the SSWUG Virtual Conference, which is held twice each year.

More from this author