Планувальник завдань в ASP.NET (исходники), HTML, XML, DHTML, Інтернет-технології, статті

Введення


Іноді перед веб розробниками виникають завдання, на перший погляд в принципі нерозв’язні якщо виходити із загального будови веб додатків і спілкування “запит-відповідь”. Однією з таких завдань є завдання реалізації системи повторюваних завдань, тобто завдань, які виконуються через певні проміжки часу або в зазначений час, наприклад регулярне очищення таблиць в базі даних або відсилання 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 і вся логіка для отримання значень цих параметрів повинна бути написана в цьому методі. В даному випадку параметри будуть передаватися у вигляді елемента і для отримання їх значень я скористався методом 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>

*

*