Планувальник завдань в ASP.NET (вихідні коди)

Введення


Іноді перед веб розробниками виникають завдання, на перший погляд у принципі нерозв'язні якщо виходити із загального будови веб додатків і спілкування "запит-відповідь". Однією з таких завдань є завдання реалізації системи повторюваних завдань, тобто завдань, що виконуються через певні проміжки часу або в зазначений час, наприклад регулярне очищення таблиць у базі даних або відсилання email повідомлень з черги. У цьому разі, щасливі володарі виділених серверів пишуть windows сервіси або запускають консольні програми за допомогою вбудованого в Windows планувальника завдань. Трохи менш щасливим доводиться домовлятися про подібних сервісах з хостером. Ну а основна маса вирішує таку задачу шляхом смикання за допомогою того ж планувальника сторінок, в логіці яких забиваються необхідні завдання. Але на самому справі подібні завдання в ASP.NET можна реалізувати набагато простіше, не вдаючись до використання сторонніх засобів.


Короткий опис


Коротенько алгоритм вирішення такого завдання вкрай простий: при старті веб додатку запускається таймер System.Threading.Timer, в колбек методі якого і пишеться необхідна логіка завдання. І тоді коллбек метод таймера буде викликатися через вказаний проміжок часу весь час роботи веб додатку. Про всяк випадок я наведу приклад описаного вище коду, що запускає з інтервалом в одну хвилину метод, пишучий повідомлення про свій виклик до лог файл (код розташований у файлі global.asax).






  void Application_Start(object sender, EventArgs e)
{
System.Threading.Timer t = new System.Threading.Timer (new System.Threading.TimerCallback (DoRun), null, 0, 60000);
}
private void DoRun(object state)
{
System.IO.StreamWriter sw = new System.IO.StreamWriter (new System.IO.FileStream (Server.MapPath ("test.log"), System.IO.FileMode.Append));
    sw.WriteLine(“Job started at {0}”, DateTime.Now);
    sw.WriteLine();
    sw.Close();
}

Дане рішення, безумовно, має право на існування, але воно не відрізняється ні особливою витонченістю, ні зручністю застосування – адже кожного разу для зміни логіки роботи завдань доведеться перезбирати всі веб додаток. Тому, взявши за основу вишенапісанного я постараюся реалізувати більше легкотравну систему для планувальника завдань, однаково придатну для використання як в веб, так і в windows додатках.


Сама система складається з 2-х модулів – загального модуля управління завданнями і модуля управління окремим завданням. Завдання ж являють собою класи, що реалізують інтерфейс ITask з єдиним методом Run (), в якому і реалізується логіка завдання. Ну а конфігурація всієї системи реалізована у вигляді секції конфігураційного файлу.


Програмна реалізація системи повторюваних завдань


Короткий відступ. Весь код цієї статті написаний на C # 2.0 і. NET 2.0. Але в архіві, що містить вихідний код статті, знаходяться проекти і для. NET 2, і для. NET 1.1.


Завдання


Почнемо ми, мабуть, з найпростішого – опису інтерфейсу ITask для завдань. Як я вже згадав вище, цей інтерфейс містить єдиний метод Run з параметром типу XmlNodeList для передачі параметрів завданням. Ви запитаєте "а чому тип XmlNodeList?" Тут все досить просто – так як налаштування завдань проводиться в. Config файлі, ми можемо просто передати частину цієї настройки, що відноситься до даного завдання. Чесно кажучи, я думав про застосування чого-небудь більш зручного (наприклад, того ж Hashtable), але при реалізації завдання Scheduler, про який я розповім в кінці статті, я зіткнувся з тим, що для більшого зручності краще не обмежувати можливість завдання параметрів. Отже, весь код інтерфейсу ITask буде таким






  public interface ITask
{
    void Run(XmlNodeList parameters);
}

Класи конфігурації


Тепер створимо конфігураційні класи і реалізуємо клас секції конфігураційного файлу. Фактично конфігурація системи будуть складатися з трьох рівнів – рівня налаштувань самої системи, рівня налаштувань окремих завдань і рівня параметрів завдань. Про параметри завдань я вже писав вище – вони будуть задаватися у вільному вигляді і робота з ними цілком і повністю буде виконуватися в самій завданню. Установки окремої завдання містять ім'я і тип завдання (ім'я завдання повинна бути унікальною серед всіх завдань системи), мітку про те, чи включена завдання, мітку про те, чи використовує завдання загальний таймер системи або свій власний таймер і інтервал (у секундах) виклику завдання. Ну а в налаштуваннях самої системи є мітка про те, чи включена система, мітка про використання загального таймера і інтервал спрацьовування загального таймера в секундах. І, крім того, налаштування верхнього рівня містять у собі настройки більш низького рівня, тобто в налаштування завдання включаються і параметри завдання, а в налаштуваннях системи містяться налаштування всіх завдань.


І якщо вже зайшла розмова про загальний таймері, то доведеться розповісти, що це таке. У загальному випадку для кожного завдання запускається свій власний таймер з унікальними налаштуваннями інтервалу спрацьовування. Але так як чимала кількість рутинних завдань частенько викликається з одним і тим же інтервалом – має сенс об'єднати їх виклики в одному таймері. Але, на відміну від власного таймера завдання цей таймер запускається в головному керуючому класі системи.


У принципі по налаштуванню все і тепер ми можемо поглянути на код, що вийшов






  public class TasksSettings
{
    public bool IsSingleThreaded;
    internal int Seconds;
    internal bool Enabled;
    public Collection<TaskSettings> Tasks;
    public TasksSettings()
    {
        IsSingleThreaded = true;
        Enabled = true;
        Seconds = 60;
        Tasks = new Collection<TaskSettings>();
    }
}
public class TaskSettings
{
    public string Name;
    public string Type;
    public bool IsSingleThreaded;
    public bool Enabled;
    public int Seconds;
    public XmlNodeList XmlParameters;
    public TaskSettings()
    {
        Name = “”;
        Type = “”;
        Enabled = true;
        IsSingleThreaded = true;
        Seconds = 0;
        XmlParameters = null;
    }
}

У наведеному коді клас TasksSettings – це налаштування системи, TaskSettings – настройки окремого класу, ну а XmlParameters – параметри завдання.


Клас секції конфігураційного файлу теж не являє собою нічого складного і про принципи його створення я вже нещодавно писав у статті про створення системи статистики сайту. Тому просто наведу цей клас без додаткових пояснень.






  class TasksSectionHandler : IConfigurationSectionHandler
{
object IConfigurationSectionHandler.Create (object parent, object configContext, XmlNode section)
    {
        TasksSettings ret = new TasksSettings();
ret.IsSingleThreaded = bool.Parse (section.Attributes ["isSingleThreaded"]. Value);
        if(ret.IsSingleThreaded)
ret.Seconds = Int32.Parse (section.Attributes ["seconds"]. Value);
ret.Enabled = bool.Parse (section.Attributes ["enabled"]. Value);
        foreach (XmlNode node in section.ChildNodes)
        {
            TaskSettings task = new TaskSettings();
            task.Name = node.Attributes[“name”].Value;
            task.Type = node.Attributes[“type”].Value;
task.IsSingleThreaded = bool.Parse (node.Attributes ["isSingleThreaded"]. Value);
task.Enabled = bool.Parse (node.Attributes ["enabled"]. Value);
            if (!task.IsSingleThreaded)
task.Seconds = Int32.Parse (node.Attributes ["seconds"]. Value);
            task.XmlParameters = node.ChildNodes;
            ret.Tasks.Add(task);
        }
        return ret;
    }
}

Ну і наостанок, мабуть, потрібно привести шматок конфігураційного файлу з описом якоїсь задачі






  <configuration>
  <configSections>
<section name="tasks" type="Dimon.Tasks.TasksSectionHandler, Dimon.Tasks"/>
  </configSections>
<tasks seconds="60" isSingleThreaded="true" enabled="true">
<task name="test" type="Dimon.Tasks.Tests.TestTask, Dimon.Tasks.Tests" isSingleThreaded="true" enabled="true" seconds="60">
<param name="StringParam" value="value" type="System.String" />
<param name="IntParam" value="150" type="System.Int32" />
<param name="DateTimeParam" value="2005-11-11" type="System.DateTime" />
    </task>
  </tasks>
</configuration>

Тепер, коли з конфігурацією покінчено, настала пора перейти до класу управління завданням.


Класи управління завданнями


Даний клас, по суті, є обгорткою над класом завдання і служить для створення екземпляра класу завдання і виклику методу Run цього примірника. Крім того, саме цей клас містить таймер для завдань, працюють з власним таймером. Ну і додатково до всього цей клас виставляє назовні властивості запуску завдання для керуючого класу. Але перш ніж дивитися на клас управління завданням, давайте розглянемо загальний принцип роботи системи.


Як я вже згадував раніше, основну роботу з управління планувальником завдань виконує окремий клас – TaskEngine. Цей Сінглтон (singleton) клас має 2 методи Start () і Stop () соотв. для запуску системи і для її зупинки. Метод Start () читає конфігурацію планувальника задач і на основі даних з конфігурації формує список примірників класу управління завданням TaskLauncher і запускає таймери завдань (Якщо система не працює з єдиним таймером або якщо дане завдання використовує власний таймер). Крім того, у методі Start (), якщо це необхідно, створюється і запускається загальний таймер системи. Метод Stop (), відповідно, знищує всі класи управління завданнями в списку і загальний таймер пданіровщіка (якщо він використовується). Ну і, природно, клас TaskEngine містить коллбек метод загального таймера, в якому відбувається виклик активних завдань, що використовують загальний таймер.


Клас управління завданнями TaskLauncher у свою чергу, використовуючи налаштування завдання, створює екземпляр класу зазначеного в налаштуваннях типу і у своїх властивостях виставляє назовні деякі властивості завдання, як те ім'я і тип завдання, мітку використання завданням загального таймера, мітку, активна чи завдання й ознака того, що завдання виконується. Ну і, крім того, цей клас містить методи для створення екземпляра класу завдання, запуску завдання (виклику методу Run екземпляра класу) і роботу з таймером (у разі, якщо завдання працює з власним таймером).


Ось, мабуть, і все про загальний опис роботи планувальника завдань і ми можемо, нарешті, перейти до розгляду коду класів. Для початку я наведу код класу управління завданням TaskLauncher.


Опис властивостей даного класу я вже дав, тому наведу цей код без коментарів






  public class TaskLauncher : IDisposable
{
    private ITask task;
    private bool isRunning;
    private Type taskType;
    private Timer timer;
    private bool disposed;
    public TaskSettings settings;
    public bool Enabled
    {
        get { return settings.Enabled; }
    }
    protected int Interval
    {
        get { return settings.Seconds * 1000; }
    }
    public bool IsRunning
    {
        get { return isRunning; }
    }
    public Type TaskType
    {
        get { return taskType; }
    }
    public string Name
    {
        get { return settings.Name; }
    }
    public bool SingleThreaded
    {
        get { return settings.IsSingleThreaded; }
    }
    public TaskLauncher(TaskSettings task)
    {
        taskType = Type.GetType(task.Type);
        settings = task;
    }

Більш цікавий метод GetInstance (), який повертає примірник класу завдання. У випадку, якщо з якихось причин примірник класу завдання створити не виходить – цей метод крім усього іншого вимикає завдання






      private ITask GetInstance()
    {
        if (Enabled && task == null)
        {
            if (taskType != null)
task = Activator.CreateInstance (taskType) as ITask;
            settings.Enabled = task != null;
            if (!Enabled)
            {
                this.Dispose();
            }
        }
        return task;
    }

Метод Run () запуску завдання також не відрізняється особливою складністю – він викликає метод GetInstance () для отримання екземпляра класу завдання й у разі повернення екземпляра класу просто викликає його метод ITask.Run()






      public virtual void RunTask()
    {
        isRunning = true;
        ITask task = this.GetInstance();
        if (task != null)
        {
            try
            {
                task.Run(settings.XmlParameters);
            }
            catch
            {
            }
        }
        isRunning = false;
    }

Крім цього в класі є пара методів для роботи з власним таймером – метод ініціалізації таймера і коллбек метод самого таймера, викликає тільки що розглянутий нами метод Run ()






      public void InitializeTimer()
    {
        if (timer == null && Enabled)
        {
timer = new Timer (new TimerCallback (timer_Callback), null, this.Interval, this.Interval);
        }
    }
    private void timer_Callback(object state)
    {
        if (Enabled)
        {
            timer.Change(-1, -1);
            RunTask();
            if (Enabled)
            {
                timer.Change(this.Interval, this.Interval);
            }
            else
            {
                this.Dispose();
            }
        }
    }

Ну і останній метод цього класу, Dispose (), зачищає таймер у разі його наявності






      public void Dispose()
    {
        if ((timer != null) && !disposed)
        {
            lock (this)
            {
                timer.Dispose();
                timer = null;
                disposed = true;
            }
        }
    }

Тепер можна перейти до розгляду коду класу управління системою TaskEngine. Для роботи цей singleton клас використовує список класів управління завданнями і таймер. Крім того, в ньому є властивості для доступу до поточних завдань системи. Вообщем це простіше показати кодом, ніж описати;)






  public sealed class TaskEngine
{
    static TaskEngine taskengine = null;
    static readonly object padlock = new object();
    private bool isStarted;
    private bool _isRunning;
    private int Interval;
    private Dictionary<string, TaskLauncher> taskList;
    private Timer singleTimer;
    private TaskEngine()
    {
taskList = new Dictionary <string, TaskLauncher> ();
        Interval = 60000;
    }
    public static TaskEngine Instance
    {
        get
        {
            lock (padlock)
            {
                if (taskengine == null)
                {
                    taskengine = new TaskEngine();
                }
                return taskengine;
            }
        }
    }
public Dictionary <string, TaskLauncher> CurrentJobs
    {
        get
        {
            return this.taskList;
        }
    }
    public bool IsTaskEnabled(string taskName)
    {
        if (!this.taskList.ContainsKey(taskName))
        {
            return false;
        }
        return this.taskList[taskName].Enabled;
    }

Більш цікавий метод Start (), що запускає систему. У цьому методі завантажуються налаштування планувальника завдань, на основі цих налаштувань створюється список класів управління завданнями і запускаються всі необхідні для роботи таймери (внутрішні таймери для завдань і загальний таймер для всієї системи).






      public void Start()
    {
        if (isStarted)
            return;
        isStarted = true;
        lock (padlock)
        {
            if (taskList.Count != 0)
            {
                return;
            }
TasksSettings settings = (TasksSettings) WebConfigurationManager.GetSection ("tasks");
            if (!settings.Enabled)
                return;
            if (settings.IsSingleThreaded)
                this.Interval = settings.Seconds * 1000;
            foreach (TaskSettings t in settings.Tasks)
            {
                if (!taskList.ContainsKey(t.Name))
                {
                    TaskLauncher task = new TaskLauncher(t);
                    taskList.Add(t.Name, task);
if (! task.SingleThreaded / /! settings.IsSingleThreaded)
                    {
                        task.InitializeTimer();
                    }
                }
            }
            if(settings.IsSingleThreaded)
this.singleTimer = new Timer (new TimerCallback (this.call_back), null, this.Interval, this.Interval);
        }
    }

Метод Stop () ж робить зворотні дії – знищує всі класи управління завданнями і зупиняє і знищує загальний таймер.






      public void Stop()
    {
        if (isStarted)
        {
            lock (padlock)
            {
foreach (TaskLauncher task in this.taskList.Values)
                {
                    task.Dispose();
                }
                this.taskList.Clear();
                if (this.singleTimer != null)
                {
                    this.singleTimer.Dispose();
                    this.singleTimer = null;
                }
            }
        }
    }

Ну і останній метод – коллбек метод таймера – запускає активні завдання, використовують загальний таймер






      private void call_back(object state)
    {
        if (_isRunning)
            return;
        _isRunning = true;
        this.singleTimer.Change(-1, -1);
        foreach (TaskLauncher task in this.taskList.Values)
        {
            if (task.Enabled && task.SingleThreaded)
            {
                task.RunTask();
            }
        }
this.singleTimer.Change (this.Interval, this.Interval);
        _isRunning = false;
    }

Все, планувальник завдань готовий і тепер можна приступати до тестів.


Тестуємо планувальник завдань


Як я вже згадував раніше, дана система однаково добре буде працювати як в веб, так і в windows додатках. І щоб не ускладнювати собі життя тести працездатності планувальника завдань ми будемо робити в консольному додатку – і наочніше вийде, і з правами проблем уникнемо.


Насамперед для тестів потрібно написати клас задачі, який реалізує інтерфейс ITask. Це буде простенький клас, просто виводить дату початку своєї роботи і всі передані йому параметри. Вообщем не буду розвозити кашу по столу, а покажу код цього класу






  class TestTask : Dimon.Tasks.ITask
{
    void Dimon.Tasks.ITask.Run(XmlNodeList parameters)
    {
Console.WriteLine ("Task started at {0}", DateTime.Now);
        Console.WriteLine(“Parameters:”);
        foreach (XmlNode param in parameters)
        {
Console.WriteLine ("{0} {1}", param.Attributes ["name"]. Value, Convert.ChangeType (param.Attributes ["value"]. Value, Type.GetType (param.Attributes ["type" ]. Value)));
        }
        Console.WriteLine();
        Console.WriteLine();
    }
}

Як ви, я сподіваюся, пам'ятаєте, параметри завдання передаються в метод Run у вигляді XmlNodeList і вся логіка для отримання значень цих параметрів повинна бути написана в цьому методі. У даному випадку параметри будуть передаватися у вигляді елемента <param name="імя параметра" value="значеніе параметра" type="тіп параметра" /> і для отримання їх значень я скористався методом Convert.ChangeType ().


Код конфігурування планувальника завдань в. Config файлі програми буде точно таким же, як я наводив вище. Про всяк випадок повторюся






  <?xml version=”1.0″ encoding=”utf-8″ ?>
<configuration>
  <configSections>
<section name="tasks" type="Dimon.Tasks.TasksSectionHandler, Dimon.Tasks"/>
  </configSections>
<tasks seconds="60" isSingleThreaded="true" enabled="true">
<task name="test" type=" Dimon.Tasks.Tests.TestTask, Dimon.Tasks.Tests" isSingleThreaded="true" enabled="true" seconds="60">
<param name="StringParam" value="value" type="System.String" />
<param name="IntParam" value="150" type="System.Int32" />
<param name="DateTimeParam" value="2005-11-11" type="System.DateTime" />
    </task>
  </tasks>
</configuration>

У даному випадку планувальник налаштований на виконання завдання раз на 60 секунд і для цього використовується загальний таймер.


Залишилося написати код для запуску планувальника завдань у додатку






      static void Main(string[] args)
    {
        TaskEngine.Instance.Start();
        Console.ReadLine();
        TaskEngine.Instance.Stop();
    }

І можна запустити вийшло додаток і насолоджуватися результатом – тепер раз на хвилину виконується в планувальнику завдань завдання буде виводити повідомлення в консоль.


Ось, мабуть, і все, що можна було написати про створення планувальника завдань для. NET додатки. Все, та не зовсім – дана система все таки не дозволяє задавати виклик завдань за розкладом, а тільки лише за таймером. І ставити таймер для, наприклад, завдання, що виконується раз на день або, ще гірше, раз на тиждень як-то не зовсім правильно (та й не факт, що це завдання взагалі буде виконуватися виходячи з того, що таймер прив'язаний до додатка, яке має особливість перевантажуватися). Тому описаний вище планувальник завдань необхідно розширити.


Завдання "Завдання за розкладом"


Можна було б додати необхідний функціонал в саму систему планувальника завдань, але мені здалося більш правильним поступити інакше – реалізувати завдання "Завдання за розкладом".


Ідея реалізації цього завдання дуже проста – параметрами завдання задаються завдання з розкладом, а при спрацьовуванні таймера завдання обчислюється необхідність запуску тієї або іншої заданої завдання. Фактично виходить реалізація написаного вище планувальника завдань у мініатюрі з урахуванням додаткових параметрів у вигляді завдання розкладу запуску завдань.


Я не буду занадто сильно заглиблюватися в завдання розкладу для завдань, щоб не ускладнювати код. Додатково до стандартних параметрах завдань, описаним раніше в класі TaskSettings, кожне завдання може запускатися один раз на якийсь проміжок часу – годину, день, тиждень або місяць у вказаний день (якщо потрібно) і час. Для завдання цих значень в конфігураційному файлі служать атрибути duration і startAt. При цьому duration приймає значення з перерахування






      internal enum Duration
    {
        Hour,
        Day,
        Week,
        Month
    }

А значення параметра startAt задається за наступним принципом:



Відповідно, клас налаштувань завдань, поставлених в цьому завданні, буде виглядати так:






      public class SchedulerTaskSettings : TaskSettings
    {
        internal Duration duration;
        public string startAt;
        public SchedulerTaskSettings()
        {
        }
    }

Аналогічно потрібно також зробити клас управління завданнями – спадкоємець класу TaskLauncher. Так як в даній задачі завдання крім усього іншого ще і мають параметри, що вказують час їх запуску – роботу з ними необхідно додати в цей клас. Ці зміни торкнуться тільки конструктора класу і методу Run (), в яких буде проводитися обчислення часу наступного запуску завдання. Код цього класу досить простий, тому наведу його без коментарів






      public class SchedulerTaskLauncher : TaskLauncher
    {
        private DateTime startTime;
        private Duration duration;
        public DateTime StartTime
        {
            get { return startTime; }
        }
public SchedulerTaskLauncher (SchedulerTaskSettings task)
            : base(task)
        {
            string[] starttime = task.startAt.Split(“,”);
            TimeSpan time = TimeSpan.MinValue;
            DateTime date = DateTime.MinValue;
            switch (task.duration)
            {
                case Duration.Hour:
                    date = DateTime.Now;
                    int min = int.Parse(starttime[0]);
                    if (min < date.Minute)
time = new TimeSpan (date.Hour + 1, min, 0);
                    else
time = new TimeSpan (date.Hour, min, 0);
                    date = DateTime.Today;
                    break;
                case Duration.Day:
                    time = TimeSpan.Parse(starttime[0]);
                    date = DateTime.Today;
                    break;
                case Duration.Week:
                    time = TimeSpan.Parse(starttime[1]);
date = DateTime.Today.AddDays (- ((int) DateTime.Today.DayOfWeek)). AddDays ((int) (DayOfWeek) Enum.Parse (typeof (DayOfWeek), starttime [0]));
                    break;
                case Duration.Month:
                    time = TimeSpan.Parse(starttime[1]);
date = DateTime.Today.AddDays (-DateTime.Today.Day). AddDays (int.Parse (starttime [0]));
                    break;
            }
            startTime = date.Add(time);
            duration = task.duration;
        }
        public override void RunTask()
        {
            base.RunTask();
            switch (duration)
            {
                case Duration.Hour:
                    startTime = startTime.AddHours(1);
                    break;
                case Duration.Day:
                    startTime = startTime.AddDays(1);
                    break;
                case Duration.Week:
                    startTime = startTime.AddDays(7);
                    break;
                case Duration.Month:
                    startTime = startTime.AddMonths(1);
                    break;
            }
        }
    }

Попередня робота завершена, залишилося реалізувати метод Run () завдання Scheduler. І цей код потребує деяких коментарів.


По-перше тому що саме завдання Scheduler працює по таймеру, то необхідно визначити яким чином обчислювати момент запуску тієї або іншої задачі в Scheduler-е. Код цей досить простий – якщо різниця між поточним часом і часом запуску задачі більше або дорівнює 0 і менше інтервалу запуску завдання Scheduler, то ця задача повинна бути запущена на виконання. Але для цього самої завданню Scheduler необхідно передати в параметрі interval значення, рівне значенню керуючого параметра задачі seconds (або значення керуючого параметра seconds всієї системи, якщо завдання Scheduler використовує загальний таймер).


По-друге ж так як параметри передаються у завдання при виклику методу Run (), то в ньому (при необхідності) потрібно проініціалізувати параметри завдання і закеширувати їх у полях класу.


У результаті код завдання Scheduler у мене особисто вийшов ось таким






      class Scheduler : ITask
    {
        private bool isInitialized = false;
        private int interval;
private Dictionary <string, SchedulerTaskLauncher> taskList;
        public void Run(XmlNodeList parameters)
        {
            if (!isInitialized)
            {
taskList = new Dictionary <string, SchedulerTaskLauncher> ();
                foreach (XmlNode param in parameters)
                {
if (param.Name == "param" & & param.Attributes ["name"]. Value == "interval")
interval = int.Parse (param.Attributes ["value"]. Value);
                    else if (param.Name == “task”)
                    {
SchedulerTaskSettings task = new SchedulerTaskSettings ();
task.Name = param.Attributes ["name"]. Value;
task.Type = param.Attributes ["type"]. Value;
task.duration = (Duration) Enum.Parse (typeof (Duration), param.Attributes ["duration"]. Value);
task.startAt = param.Attributes ["startAt"]. Value;
task.XmlParameters = param.ChildNodes;
if (! taskList.ContainsKey (task.Name))
                        {
SchedulerTaskLauncher t = new SchedulerTaskLauncher (task);
                            taskList.Add(t.Name, t);
                        }
                    }
                    else
throw new ArgumentException ("Unknown parameter:" + param.Name);
                }
                isInitialized = true;
            }
foreach (SchedulerTaskLauncher task in taskList.Values)
            {
                TimeSpan t = DateTime.Now – task.StartTime;
if (t.TotalSeconds> = 0 & & t.TotalSeconds <= interval)
                {
                    task.RunTask();
                }
            }
        }
    }

Все, завдання Scheduler для запуску завдань за розкладом готова. Залишилося навести приклад конфігураційного файлу цього завдання






 <tasks seconds="60" isSingleThreaded="true" enabled="true">
<task name="scheduler" type="Dimon.Tasks.Scheduler, Dimon.Tasks" isSingleThreaded="false" enabled="true" seconds="60">
      <param name=”interval” value=”60″ />
<task name="test" type="Dimon.Tasks.Tests.TestTask, Dimon.Tasks.Tests" duration="Hour" startAt="30">
<param name="StringParam" value="value" type="System.String" />
<param name="IntParam" value="150" type="System.Int32" />
<param name="DateTimeParam" value="2005-11-11" type="System.DateTime" />
      </task>
    </task>
  </tasks>

У цьому прикладі завдання Dimon.Tasks.Tests.TestTask буде викликатися до 30 хвилин кожної години.


Висновок


Все, тепер вже точно все:). Планувальник завдань написаний, Scheduler до нього прироблений – що ще потрібно для нормальної роботи? Звичайно ж дана реалізація не може претендувати на лаври най-най – комусь не сподобається урізана можливість завдання розкладу в задачі Scheduler, хтось захоче в саму систему ввести рівень таймерів для об'єднання завдань. Ну так дерзайте – зробити подібні розширення не так вже й складно:).


При переписуванні планувальника завдань в сучасний вигляд безліч ідей було почерпнуто з реалізації подібного функціоналу в Community Server 2.0.


Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*