О чем проект
По роду своей работы мне иногда приходится выполнять одну и ту же работу но немного изменять что-то перед ее началом. Допустим нужно обновить какой-то отчет и делать это нужно каждую неделю\месяц\декаду. Соответственно каждую неделю мне нужны какие-то свежие данные и я должен их вручную как-то изменить. А бывает, нужно проверить их корректность и полноту. Или нужно отправлять какой-то файл на какие-то адреса, причем каждый день этот файл разный. И т.д. В общем все то что нужно делать почти вручную. Да и почту у каждого есть такая часть работы - каждую неделю или месяц офисные работники сдают какие-то отчеты, на формирование которых уходит немало времени.
Исходя из этого я решил написать выполняющего планировщика. Его суть - выполнять какие-то действия в определенное время и с определенной периодичностью. Решил написать потому что не нашел ни одного приложения, которое удовлетворяло моим требованиям. А самым важным требованием было то, что я мог бы произвольно менять, добавлять, удалять задания планировщика. Были варианты например с Job-ми в MS SQL. Я слышал что там можно выполнять внешние скрипты. Это было уже лучше, но тоже не удовлетворяло. Если кто-то хочет знать почему - я могу пояснить, но это уже будет другой спор.
Ну и плюс мне было интересно самому это сделать
Наказал себя требованиями
Когда я планировал на бумаге проект были выдвинуто много требований. Приложение должно было уметь выполнять несколько запланированных задач одновременно. Поэтому приложение многопоточное. Увлекшись потоками я добавил их везде где мог. Например есть отдельный поток опроса всех задач на время запуска и пересчета времени (если опоздали с выполнением). Также последнее время я стараюсь отделить вычисления от интерфейса, поэтому обновление интерфейса (всякая текстовая информация) - тоже в отдельном потоке. Из-за этого конечно много проблем, и не все получается и не сразу если получается вообще.
Но самое важное - задачи не должны прописываться в самой программе. Это была одна из основных целей. Поэтому я решил сделать так - на задачу создается dll-ка, в которой прописан весь функционал. А мы только планируем что-то по этой dll. Таким образом ничего не придется переписывать в самом приложении - меняем только модуль определенной задачи. Я понимал что универсального планировщика мне не создать, и что программировать я не брошу, поэтому библиотек для задач я себе уж как-нибудь наклепаю. Ну и еще кое-что, что я не могу сказать сподвигло меня делать именно так.
Немного о работе
С приложением обязательно идет xml (мой любимый) файл где описываются задачи. В него можно добавлять новые задачи, редактировать старые, по-новому планировать их и т.д. В этом файле описывается и модуль выполнения.
Вот параметры файла с описанием.
CountTask - количество планируемых задач. Планироваться будут первые n задач в файле (n=атрибуту val этой ноды)
Дальнейшее справедливо для каждого Task-а
Name - имя задачи
Activate - активна ли задача. Если задача не активна, она никогда не планируется. Но в списке висит.
StopAllThreadForExec - не тестировал на все сто. Если в атрибуте val стоит -1, то при запуске этой задачи приостанавливаются все остальные пока эта задача не выполнится. Это нужно когда две задачи работают например с одними и теми же данными. И нужно чтобы только одна задача работала с ними. Пробуем тестить, но полностью не отвечаю )
LastExecTime - все атрибуты описывают последнее время выполнения задач. Лениво парсить одну строку, так как надо много проверять на ошибки. Да и в данном виде удобнее считывать и записывать в файл. Данные переписываются при выполнении каждой задачи
PeriodExec - в том же формате описывают периодичность выполнения. Если нужно запускать каждую минуту - то везде ставим нули, кроме minute, ну и т.д. Атрибут года пока не читается за ненадобностью.
NextExecTime - следующее время запуска. От этого времени всегда отсчитывается следующее время выполнения. Править его не рекомендуется. Пишем туда только раз при планировании задачи. Дальше оно само пересчитывается. В общем трогать его без надобности не нужно.
ModulePath - путь к библиотеке с кодом задачи. Ну тут все понятно.
LogFilePath - лог файл задачи. Пока не ведется. А может и не будет, думаю лучше будет вести один лог на все задачи
FinishCode - в атрибуте val - сам код последнего выполнения задачи. В laststep - последний шаг выполнения. Здесь должно быть 0 если последнее выполнение задачи в целом прошло успешно (в val будет записан 0). Если какой-то шаг задачи прошел с ошибкой, то в laststep запишется на какой конкретно шаге это произошло, а в val попадет...пока что просто 1. Далее добавим нормальные коды ошибок.
OnErrorTask - работает обычно в связке с FinishCode. Атрибут StartFromLastStep говорит, что при следующем запуске задачи по плану нужно его выполнять с последнего шага. Это если будет -1, иначе задача будет выполняться полностью. Это нужно если например необходимо перезапустить задачу после ошибки вручную с последнего шага. Атрибут RecelcNextTime - говорит пересчитывать ли следующее время запуска если какой-то шаг оборвался. Если здесь стоит 0, значит и код ошибки не равен 0 (FinishCode), то задача не будет планировать больше, пока не сбросятся параметры. Если -1, то будет пересчет следующего времени запуска (соответственно в зависимости от параметров следующий запуск может начаться заново, или с последнего шага, я уже об этом говорил)
AskAboutStart - думаю мало пригодится, но сделал. Если здесь стоит -1, то будет спрашиваться нужно ли выполнять задачу, если ответят нет, то не выполняется и переносится на следующий запуск.
CountSteps - количество шагов в задаче. Тут понятно
Далее идет описание шагов. Их может быть произвольное количество, все они должны быть реализованы в библиотеке задачи. Выполняться будут первые n шагов, где n=CountSteps
Name - Это имя функции в библиотеке. Я его считаю и именем шага. Но важно помнить - это имя функции.
LastExecTime - последнее время выполнения шага
TimeExecuteInSeconds - последнее время выполнения шага в секундах
FinishCode - возвращенный код выполнения
Active - активен ли шаг. Если не активен, то он пропускается и переходит к следующему если нужно.
ExitIfFailedCode - если здесь стоит -1, то выполнение задачи прекратится на этом шаге, если задача вернула "плохой код возврата". В задаче все это отразится - в FinishCode запишется 1, и соответственно в шаге тоже. При выбросе из шага действие будет зависеть от параметров OnErrorTask
FailedCodes - это "плохие" коды возвратов. Т.е. коды, по которым считается что шаг фигово выполнился или не выполнился. В count - указывается количество разных кодов, а в code0, code1...coden указываются числовые значения кодов, где n=count.
Вот и все описание.
Значит понимаем что реализуем в библе функции, возвращающие целочисленные значения. И если нужно указываем какие коды плохие на какие не обращать внимания.
На что не стоит обращать внимания
В первую очередь на интерфейс приложения. Он очень кошерный и сейчас только для того чтобы видеть что что-то хоть делается. Поэтому к интерфейсу и принципу его работы не придераемся. В последствии это все скорее всего перейдет в библиотеку.
В приложении почти нет проверки ошибок на дурака. Поэтому не стоит тестировать явные ошибки. Всякие там текстовые коды, отсутствие нодов и т.д. Это проверяется но не везде и не всегда.
На что обращать внимание
Ну на функциональность конечно. Нужно знать все ли что я описал работает. В частности все что указано в параметрах xml-файла должно учитываться и работать.
Протестировать нужно сверху донизу - приостановки потоков, обработку кодов возвратов, вопросы о выполнении, пересчеты времени выполнения и запусков и т.д.
Я жду...
Конструктивной критики и предложений а также мнений. Замечаний по работе. Ошибки. В общем то можно устраивать краш-тест. Ну и т.д.
Замечания
Библиотека должна быть нативная. Я пробовал писать на FLDLL сделанной Хакером, но приложение вылетает. Еще не разбирался почему. Если есть вопросы по функционалу - то я все расскажу, даже код могу дать, но он сишный. Даю пробную библу с двумя функциями, где есть выводится сообщения для теста. Ну и после Ваших замечаний я буду переделывать приложение - поэтому их тоже неплохо бы тестировать. Да, пути к библиотеке нужно будет прописать, так как там сейчас не действительные. Чтобы запустить цикл планирования в меню Сервис жмем "запустить цикл обработки задач"
Спасибо всем отозвавшимся заранее.