C # - отметки таймера короче, чем указано в интервале, в результате чего параллельные задания выполняются параллельно - PullRequest
0 голосов
/ 18 октября 2019

У меня есть служба Windows, которая работает каждые 4 минуты. Когда таймер показывает, запускает ли DataImporter, DataImporter имеет несколько «заданий», которые он может выполнять на каждом тике, например, есть задание ProcessData и задание RetreiveData:

  • Задание RetreiveDataбудет обращаться к стороннему API и сохранять данные в БД для обработки.
  • Задание ProcessData будет извлекать данные из БД и обрабатывать их в нашей полезной БД и т. д.

Как толькоDataImporter запускается, он проверяет таблицу БД с именем ScheduledJob - она ​​имеет ряд функций планирования, таких как FrequencyInterval, время ActiveStart / Stop, время StartedLastRun. Таблица ScheduledJob имеет флаг «InProgress», этот флаг останавливает DataImport, который подхватывает это задание, когда оно уже выполняется.

Существует постоянная проблема, когда задание выбирается дважды, на расстоянии нескольких секунд друг от друга, а затем запускается одновременно, что приводит к ряду ограничений БД при попытке вставить идентичные записи. Я не совсем уверен, как он может поднять две работы одновременно, отметка времени составляет 4 минуты, поэтому теоретически он не должен иметь возможности даже смотреть на потенциальные задания, которые могут выполняться, как он может выполнять их обес интервалом в несколько секунд?

Необходимо, чтобы задания RetrieveData и ProcessData выполнялись параллельно, поэтому я не могу приостанавливать таймер во время выполнения задания.

Служба:

public partial class DataImport : ServiceBase
{
    private int _eventId = 0;
    readonly Timer _serviceTimer = new Timer(240000);

    public DataImport()
    {
        InitializeComponent();
        ImportServiceEventLog.Source = ServiceSource.DATA_IMPORT_SERVICE.ToString() + Global.ReleaseModeSource(); ;
    }

    protected override void OnStart(string[] args)
    {
        ImportServiceEventLog.WriteEntry(ServiceSource.DATA_IMPORT_SERVICE.ToString() + Global.ReleaseModeSource() + " started", EventLogEntryType.Information, _eventId++);
        _serviceTimer.AutoReset = true;
        ImportServiceEventLog.WriteEntry(ServiceSource.DATA_IMPORT_SERVICE.ToString() + Global.ReleaseModeSource() + " timer interval = " + _serviceTimer.Interval / 1000 + " seconds", EventLogEntryType.Information, _eventId++);
        _serviceTimer.Elapsed += new ElapsedEventHandler(OnTimer);
        _serviceTimer.Start();
    }

    protected override void OnStop()
    {
        ImportServiceEventLog.WriteEntry(ServiceSource.DATA_IMPORT_SERVICE.ToString() + Global.ReleaseModeSource() + " stopped", EventLogEntryType.Information, _eventId++);
    }

    public void OnTimer(object sender, ElapsedEventArgs args)
    {
        try
        {
            Run();
        }
        catch (System.Exception ex)
        {
            ImportServiceEventLog.WriteEntry(ServiceSource.DATA_IMPORT_SERVICE.ToString() + Global.ReleaseModeSource() + " error: " + ex.ToString(), EventLogEntryType.Information, _eventId++);
        }
    }

    public void Run()
    {
        using (var dataImportController = new DataImportController())
        {
            dataImportController.Run();
        }                
    }
}

DataImportController:

public class DataImportController
{
    public void Run()
    {
        // Gets all the jobs from the ScheduledJob table in the DB
        var jobs = GetJobsToRun();

        //Get all Processes (from DB)
        foreach (var job in jobs)
        {
            //Check the time it was last run - do this for each process
            if (RunJob(job))
            {
                _messaging.EventMessage("Process " + job.Name + " finished : " + DateTime.Now, ServiceSource.DATA_IMPORT_SERVICE);
            }
        }
    }

    public bool RunJob(ScheduledJob job)
    {
        // Checks if the job is ready to run, i.e. is the InProgress flag set to false and the interval long enough since the StartedLastRun DateTime
        if (!job.IsReadyToRun())
        {
            return false;
        }

        // Set job to in progress
        job.InProgress = true;
        job.StartedLastRun = DateTime.Now;
        _scheduledJobRepository.Update(job);
        _scheduledJobRepository.SaveChanges();

        try
        {
            switch (job.Name.ToUpper())
            {
                case "RetreiveData":
                    // RUN JOB
                    break;
                case "ProcessData":
                    // RUN JOB
                    break;                    
            }

            job.InProgress = false;
            job.EndedLastRun = DateTime.Now;
            _scheduledJobRepository.Update(job);
            _scheduledJobRepository.SaveChanges();
        }
        catch (Exception exception)
        {
            _messaging.ReportError("Error occured whilst checking we are ready to run " + exception.Message, exception, null, 0, ServiceSource.DATA_IMPORT_SERVICE);
        }

        return true;
    }   
}

РЕДАКТИРОВАТЬ:

Включить Program.cs

static void Main()
{
    if (!Environment.UserInteractive)
    {
        ServiceBase[] ServicesToRun;
        ServicesToRun = new ServiceBase[]
        {
        new DataImport()
        };
        ServiceBase.Run(ServicesToRun);
    }
}

Ответы [ 3 ]

0 голосов
/ 18 октября 2019

Если перекрытие является проблемой, отключите таймер и выполните асинхронную петлю, используя Task.Delay:

async Task SomeFunc(CancellationToken token)
{
    while(!token.IsCancellationRequested)
    {
        DoWork();
        await Task.Delay(timeInterval, token);
    }
}
0 голосов
/ 18 октября 2019

Вы подписаны на событие таймера в OnStart, и не отменили подписку в OnStop.

Перемещение _serviceTimer.Elapsed += new ElapsedEventHandler(OnTimer); и инициализация автоматического сброса в конструкторе. Таймер остановки в OnStop. Это должно исправить вашу проблему. Я считаю, что ваш сервис запускается (перезапускается) более одного раза.

0 голосов
/ 18 октября 2019

попробуйте остановить таймер внутри функции OnTimer, затем перезапустите таймер после того, как он завершит выполнение вашей задачи.

...