Планировщик построен с наблюдаемых


Иногда я, чтобы запланировать некоторые задачи, и хотела иметь свою собственную многоразового планировщик. Поскольку мне нравится ГХ, я строю его на вершине.


Последующие

Есть новая версия этого Scheduler.


В Scheduler класс очень проста. Он опирается на новую реализацию моих старых CronExpression.

В настоящее время существуют только эти два метода. Заводской способ Create создает новый планировщик, который тикает в заданные промежутки времени и обеспечивает расписание как DateTimeс наблюдателями.

Задания планируются с Schedule расширение. Это требуется по cron-выражение и действие для выполнения.

public static class Scheduler
{
    public static IObservable<DateTime> Create(TimeSpan interval, IDateTime dateTime)
    {
        return
            Observable
                .Interval(TimeSpan.FromSeconds(1))
                .Select(_ => dateTime.Now());
    }

    public static IDisposable Schedule(this IObservable<DateTime> schedules, string cronExpressionString, Action<DateTime> action)
    {
        var cronExpression = CronExpression.Parse(cronExpressionString);
        return
            schedules
                .Where(cronExpression.Contains)
                .Subscribe(action);
    }
}

В DateTime абстракция поддерживается IDateTime интерфейс:

public interface IDateTime
{
    DateTime Now();
}

которая реализуется как

public class LocalDateTime : IDateTime
{
    public DateTime Now() => DateTime.Now;
}

или

public class UtcDateTime : IDateTime
{
    public DateTime Now() => DateTime.UtcNow;
}

Пример

Для того, чтобы использовать его, я просто создать планировщик, укажите, какие метки следует создать и запланировать некоторые действия:

var scheduler = Scheduler.Create(TimeSpan.FromSeconds(1), new LocalDateTime());

scheduler.Schedule("0/1 * * * * * *", schedule =>
{
    Console.WriteLine($"DEBUG: {schedule} [{Thread.CurrentThread.ManagedThreadId}]");
});

scheduler.Schedule("0/5 * * * * * *", schedule =>
{
    Console.WriteLine($"ACTION: {schedule}");
});

Вывод:

DEBUG: 13/04/2018 22:32:09 [10]
DEBUG: 13/04/2018 22:32:10 [12]
ACTION: 13/04/2018 22:32:10
DEBUG: 13/04/2018 22:32:11 [10]
DEBUG: 13/04/2018 22:32:12 [14]
DEBUG: 13/04/2018 22:32:13 [8]
DEBUG: 13/04/2018 22:32:14 [12]
ACTION: 13/04/2018 22:32:15
DEBUG: 13/04/2018 22:32:15 [8]
DEBUG: 13/04/2018 22:32:16 [12]
DEBUG: 13/04/2018 22:32:17 [8]

Там, кажется, не ракетостроение, но это, возможно, неуловимая. Может/должен этот планировщик улучшение в любом случае?



Комментарии
2 ответа


public static IObservable<DateTime> Create(TimeSpan interval, IDateTime dateTime)
{
return
Observable
.Interval(TimeSpan.FromSeconds(1))
.Select(_ => dateTime.Now());
}

Вы забыли подключить interval параметр. И, я бы рассмотреть вопрос о переименовании его resolution.

Я бы не доверял значения от выбора DatetTime::Now или DateTime::UtcNow:

// try running this for a while
var scheduler = Scheduler.Create(TimeSpan.FromSeconds(1), new LocalDateTime());
scheduler.Schedule("0/1 * * * * * *", schedule =>
{
Console.WriteLine($"ACTION: {schedule}: {schedule:ss.fff}");
});


ACTION: 2018-04-13 6:28:29 PM: 29.987
ACTION: 2018-04-13 6:28:30 PM: 30.987
ACTION: 2018-04-13 6:28:32 PM: 32.001
ACTION: 2018-04-13 6:28:33 PM: 33.001

В зависимости от CronExpression::Contains осуществления планировщик может быть пропуск задач, когда DateTime.Now.Millisecond получает около 0 или 999.

Я пытался решить это путем добавления index * interval к снимку времени, однако это решение потерпело проблема: задержка является накопительным, поэтому расписание по времени будет медленно отклоняться(отставать) от реального времени:

// dont use this
public static IObservable<DateTime> Create2(TimeSpan interval, IDateTime dateTime)
{
var snapshot = dateTime.Now();
var offset = interval.Ticks - snapshot.Ticks % interval.Ticks;

return Observable.Interval(interval)
.Delay(TimeSpan.FromTicks(offset))
.Select(x => snapshot.AddTicks(interval.Ticks * (x + 1) + offset));
}

Являются лучшим решением будет предоставить выбор времени, и проверить, если выражение падения в диапазоне:

public static IDisposable Schedule2(this IObservable<DateTime> schedules, string cronExpressionString, Action<DateTime> action)
{
var cronExpression = Regex.Match(cronExpressionString, @"^0/(?<s>\d+)");
return
schedules
.Scan(default(TimeRange), (previous, x) => new TimeRange(previous?.End, x))
.Where(cronExpression.Contains)
.Subscribe(action);
}

public class TimeRange
{
public DateTime? Start { get; }
public DateTime End { get; }

public TimeRange(DateTime? start, DateTime end)
{
this.Start = start;
this.End = end;
}
}

3
ответ дан 13 апреля 2018 в 11:04 Источник Поделиться

Xiaoy312 есть пункт о риске пропажи секунд при использовании Interval. Похоже, что каждый "тик" из Observable.Interval() ждет, когда предыдущий возвращается. Так что если наследующей останавливает поток, некоторое время, очередная "галочка" уволили слишком поздно, и вы можете пропустить одну-две секунды.

Пытаюсь начать каждый запланированную задачу в новом потоке - например, с помощью System.Timers.Timer Кажется, плохая идея, т. к. тот же хрон, вероятно, не могут работать одновременно(?).

Один из способов, чтобы не терять секунды, используя Observable.Interval выглядит следующим образом:

  Random rand = new Random(5);
IObservable<Timestamped<long>> source = Observable.Interval(TimeSpan.FromSeconds(1)).Timestamp().ObserveOn(NewThreadScheduler.Default);

IDisposable subscription = source.Subscribe(
x =>
{
Console.WriteLine("OnNext: {0}", x);
Thread.Sleep(rand.Next(0, 4001));
Console.WriteLine("After Sleep OnNext: {0}", x);
},
ex => Console.WriteLine("OnError: {0}", ex.Message),
() => Console.WriteLine("OnCompleted"));

Console.WriteLine("Press ENTER to unsubscribe...");
Console.ReadLine();
subscription.Dispose();

Это, кажется, чтобы гарантировать отсутствие скольжения в последовательности временных меток (второй-мудрый, по крайней мере), но каждый интервал может быть (нарастающим итогом) с задержкой в соответствии с предыдущими интервалами "лень". В этом случае вы не гарантированы, что работа завершена по графику, но вы не пропустите работу из-за "пропавших без вести" секунд. Однако, похоже, что время насос нагоняет опоздание, когда никаких действий задержки в текущий интервал.

1
ответ дан 14 апреля 2018 в 09:04 Источник Поделиться