(495) 925-0049, ITShop интернет-магазин 229-0436, Учебный Центр 925-0049
  Главная страница Карта сайта Контакты
Поиск
Вход
Регистрация
Рассылки сайта
 
 
 
 
 

Планировщик задач в 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}\t{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 задается по следующему принципу:

  • для частоты Hour в параметре startAt задается минута запуска задачи. То есть при конфигурации duration="Hour" startAt="15" задание будет выполняться в 15 минут каждого часа.
  • для частоты Day в параметре startAt задается время запуска задачи. Например, в случае duration="Day" startAt="15:30" задание будет выполняться в 15:30 каждый день.
  • Для частоты Week и Month в параметре startAt задаются день и время запуска задачи, разделенные запятой. При этом для Week день запуска задачи является значением перечисления System.DayOfWeek, а для Month - номер дня в месяце. Например, в случае duration="Week" startAt="Sunday, 2:00" задание будет запускаться в 2 часа ночи каждое воскресенье

Соответственно, класс настроек задач, задаваемых в этой  задаче, будет выглядеть так:

      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.



 Распечатать »
 Правила публикации »
  Написать редактору 
 Рекомендовать » Дата публикации: 22.09.2007 
 

Магазин программного обеспечения   WWW.ITSHOP.RU
Microsoft Office 365 для Дома 32-bit/x64. 5 ПК/Mac + 5 Планшетов + 5 Телефонов. Подписка на 1 год.
Microsoft 365 Business Standard (corporate)
Microsoft 365 Apps for business (corporate)
Microsoft 365 Business Basic (corporate)
Microsoft Windows Professional 10, Электронный ключ
 
Другие предложения...
 
Курсы обучения   WWW.ITSHOP.RU
 
Другие предложения...
 
Магазин сертификационных экзаменов   WWW.ITSHOP.RU
 
Другие предложения...
 
3D Принтеры | 3D Печать   WWW.ITSHOP.RU
 
Другие предложения...
 
Новости по теме
 
Рассылки Subscribe.ru
Информационные технологии: CASE, RAD, ERP, OLAP
Безопасность компьютерных сетей и защита информации
Новости ITShop.ru - ПО, книги, документация, курсы обучения
Программирование на Microsoft Access
CASE-технологии
OS Linux для начинающих. Новости + статьи + обзоры + ссылки
Программирование на Visual С++
 
Статьи по теме
 
Новинки каталога Download
 
Исходники
 
Документация
 
 



    
rambler's top100 Rambler's Top100