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.
WakeUpService class is very helpful for organizing background tasks.
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;
}
}
}
@mstijak
Copy link
Author

mstijak commented Jun 6, 2022

WakeUpService.Shared.Schedule(TimeSpan.FromMinutes(1), () => { Console.WriteLine("One minute passed..."); });

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment