Skip to content

Instantly share code, notes, and snippets.

@mstijak
Created June 6, 2022 10:48
Show Gist options
  • Save mstijak/7e10a1e73c763d3ca71ffd6e7f89ce01 to your computer and use it in GitHub Desktop.
Save mstijak/7e10a1e73c763d3ca71ffd6e7f89ce01 to your computer and use it in GitHub Desktop.

Revisions

  1. mstijak created this gist Jun 6, 2022.
    123 changes: 123 additions & 0 deletions WakeUpService.cs
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,123 @@
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading;
    using System.Threading.Tasks;

    namespace StatusApp.Util;

    public sealed class WakeUpService : IDisposable
    {
    public static WakeUpService Shared { get; } = new WakeUpService();

    private class WakeUpCall
    {
    public object Target { get; init; } = null!;
    public Func<Task> Callback { get; init; } = null!;
    public DateTimeOffset When { get; init; }
    }

    private readonly SortedDictionary<DateTimeOffset, List<WakeUpCall>> schedule;
    private readonly Dictionary<object, WakeUpCall> targets;
    private readonly object lockObject = new();
    private Timer? timer;

    public TimeSpan TimerInterval { get; set; }

    public WakeUpService()
    {
    schedule = new SortedDictionary<DateTimeOffset, List<WakeUpCall>>();
    targets = new Dictionary<object, WakeUpCall>();
    TimerInterval = TimeSpan.FromMilliseconds(1000);
    }

    public void Schedule(object target, DateTimeOffset when, Func<Task> callback)
    {
    lock (lockObject)
    {
    Cancel(target);
    var call = new WakeUpCall { Target = target, Callback = callback, When = when };
    targets.Add(target, call);
    if (!schedule.TryGetValue(when, out var calls))
    {
    calls = new List<WakeUpCall>();
    schedule[when] = calls;
    }
    calls.Add(call);

    if (timer == null)
    timer = new Timer(OnCheck, null, 0, (int)TimerInterval.TotalMilliseconds);
    }
    }

    public Action Schedule(DateTimeOffset when, Func<Task> callback)
    {
    var service = new object();
    Schedule(service, when, callback);
    return () => Cancel(service);
    }

    public void Schedule(object target, TimeSpan dueTime, Func<Task> callback)
    {
    Schedule(target, DateTimeOffset.Now.Add(dueTime), callback);
    }

    public void Schedule(TimeSpan dueTime, Func<Task> callback)
    {
    Schedule(DateTimeOffset.Now.Add(dueTime), callback);
    }

    public void OnCheck(object? state)
    {
    lock (lockObject)
    {
    while (schedule.Count > 0)
    {
    var entry = schedule.First();

    if (DateTimeOffset.Now < entry.Key)
    return;

    schedule.Remove(entry.Key);
    foreach (var call in entry.Value)
    {
    targets.Remove(call.Target);
    Task.Run(call.Callback);
    }
    }

    DisposeTimer();
    }
    }

    public void Cancel(object service)
    {
    lock (lockObject)
    {
    if (!targets.Remove(service, out var wakeUpCall))
    return;

    if (!schedule.TryGetValue(wakeUpCall.When, out var calls))
    return;

    calls.RemoveAll(x => x.Target == service);
    if (calls.Count == 0)
    schedule.Remove(wakeUpCall.When);
    }
    }

    public void Dispose()
    {
    DisposeTimer();
    }

    private void DisposeTimer()
    {
    if (timer != null)
    {
    timer.Dispose();
    timer = null;
    }
    }
    }