We write our task handler in .NET

Hello.
Does your company not like open-source?
Do you like bikes?
It was always interesting how task schedulers are arranged.
Under the cut, there’s a story about how I had to make my analogue of the famous open source scheduler quartz.net .

Background


It all started about a year and a half ago, when I moved to a new job. The project was interesting, now it is launched and even holds a good load, there are several hundred hits per second in the web part, but this, I think, will be the subject of a separate article. At a certain stage of development, there was a requirement to execute a “couple” of tasks asynchronously. Naturally, it immediately became clear that a couple of tasks would not work and a bunch of new requirements would come out. And so it happened, now the number of parallel tasks has exceeded 20 and the component has been deployed on several servers. In this article I will talk about the very first implementation, a quick and dirty solution that allowed you to quickly start.

I already had experience using quartz.net - this is the port of the well-known java component quartz to the .net platform and, frankly, the only more or less serious implementation of the .net scheduler that I know of. I also heard about Castle.Scheduling, but my hands didn’t get along. I will be glad to hear about other solutions in the comments. I, as if nothing had happened, came to my team leader with a proposal to use this solution, and then an unpleasant surprise awaited me.

It turned out that we cannot take and use some third-party component like this. After all, he did not pass the approval of the council of architects! You may have heard of these people spending half a day at meetings and the rest of the time drawing futuristic pictures that no one looks at. It also requires the approval of the security department, which should check to see if there are bookmarks in the code. But it would also be nice for lawyers to see a license agreement, you never know what. I felt like Quixote, who goes to battle with the windmills and decided to go his own way.

Of course, I realized that the deadlines are not rubber, so I climbed into the corporate FishEyeand quickly realized that he was not alone. I found two implementations of this functionality and even wanted to use one of them. Having talked with the author, I realized that he did not mind, but I would have to go to several libraries created by their team and fight all the bugs myself, as they don’t have time (read - desire) to support me.

Like any self-respecting programmer, I do not like to poke around in other people's bugs, I like to create my own. So I rolled up my sleeves and got down to business.

History


Time was running out, it was necessary to release a prototype. I must say right away that I really didn’t want to get involved with the Task Scheduler built into Windows, although I considered this option as well. You could make it look like GAE and use intervals to call web service methods. It was possible to pick quartz, but this is not a noble cause.
I wanted to make it simple so that everything worked during the working day.
But as? Well, let's set our task in the simplest way.

  1. public interface ITask
  2. {
  3.  void Execute();
  4. }
* This source code was highlighted with Source Code Highlighter.


Now we define the simplest trigger - a class that will store information about the execution interval of the task

public interface ITrigger
{
 Guid Id { get; set; }

 DateTime? NextProcessTime { get; set; }

 TimeSpan Interval { get; set;}

 ITask Task { get; set;}
}

* This source code was highlighted with Source Code Highlighter.


After that, you need to set something in the config in the spirit.

  1.  
  2.   
  3.   
  4.  
  5.  
  6.  
* This source code was highlighted with Source Code Highlighter.


And let it run every 10 seconds. Let's see how we can achieve this.

Parse the code


For our tasks to run in parallel, we need a custom thread pool.
Immediately make a reservation that .net 4.0 chips were not used, because the code was written before it was released to the masses.
Writing your pool is not an easy task, it will take a lot of time and, most likely, will lead to failure, so I took a ready-made one from a recognized guru in these matters - Jon Skeet ( there are many other useful things here) We will

wrap the handler in windows service itself , this is trivial, so we will not stop here. Let's look at the picture that explains the work of the scheduler. Let's figure out what's going on here.




First, we initialize our pool with any number of threads, which directly depends on the number of processor cores on our server. Do not forget that in addition to the scheduler, CPU time consumes both the OS and other services, so it is better to limit yourself to a reasonable amount.
We will put the tasks in the dictionary, so that if necessary it is easy to track them by identifier. In the future, this may be useful to us for persistence. The general execution scheme looks like this:
  • Download all our tasks
  • Run main loop
  • In the cycle, we run through all the tasks and put them in the queue of our pool.
  • When a task appears in the queue, the pool immediately transfers it to a free thread for execution.
  • The thread will complete the task and return to the ready state again.


To stop execution, we use Monitor and some boolean variable aka Halted, which will signal the main loop that we need to exit.

An experienced eye has probably already spotted the use of spring.net in the code. Indeed, tasks are defined as spring driven poco. This will simplify our dynamic loading and provide a number of advantages. For example, through interceptors we can log task execution time and use other AOP buns.

Let's talk a little about implementation flaws.
First, we do not limit the execution time of each task, which can lead to freezes, timeouts must always be set.
Secondly, our code is very straightforward regarding the definition of triggers. He will not allow us to carry out tasks at a given time, for example, every first day of the month or every Sunday.
I can not help but notice that in .net 4.0 a lot of tasty things appeared for multithreaded work, which will also help to improve and simplify our code.

In conclusion, a few words about concurrency. Practice shows that it is necessary to design masks taking into account the deployment on multiple servers. To do this, tasks should not have states and the elements of your workflow should also be as independent as possible. Try also to avoid lengthy tasks, it is better to do a lot of nimble.

If readers will be interested, I plan to write about the implementation of features that I find interesting. Among them is a GUI that allows you to steer a farm from such planners, see what kind of tasks are being performed, average task execution time and problems that arise. And also to stop / resume execution of tasks on any scheduler in the farm, again through the GUI.

The source code for the article can be taken here .
I sincerely recommend using quartz.net and the functionality of .net 4.0 instead of self-written solutions.

Also popular now: