Skip to content

Instantly share code, notes, and snippets.

@atifaziz
Last active September 15, 2016 11:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save atifaziz/9bd24c943e960a08821f38c17e6dfb64 to your computer and use it in GitHub Desktop.
Save atifaziz/9bd24c943e960a08821f38c17e6dfb64 to your computer and use it in GitHub Desktop.
C# Scheduler for turning a sequence of times into a async-style runnable schedule
#region Copyright (c) 2016 Atif Aziz. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#endregion
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
// ReSharper disable once CheckNamespace
// ReSharper disable once PartialTypeWithSinglePart
static partial class Scheduler
{
public static Task Run(IEnumerable<DateTime> schedule, Func<Task> job)
{
if (job == null) throw new ArgumentNullException(nameof(job));
return Run(schedule, CancellationToken.None, _ => job());
}
public static async Task Run(IEnumerable<DateTime> schedule, CancellationToken cancellationToken, Func<CancellationToken, Task> job)
{
if (schedule == null) throw new ArgumentNullException(nameof(schedule));
if (job == null) throw new ArgumentNullException(nameof(job));
using (var e = schedule.GetEnumerator())
{
var tick = CreateTicker(e);
while (true)
{
var time = await tick(cancellationToken);
if (time == null)
break;
await job(cancellationToken);
}
}
}
public static Func<CancellationToken, Task<DateTime?>> CreateTicker(IEnumerator<DateTime> schedule) =>
CreateTicker(schedule, null);
public static Func<CancellationToken, Task<DateTime?>> CreateTicker(IEnumerator<DateTime> schedule, Func<DateTime> currentTimeFunction) =>
CreateTicker(schedule, TimeSpan.FromSeconds(1.5), currentTimeFunction);
public static Func<CancellationToken, Task<DateTime?>> CreateTicker(IEnumerator<DateTime> schedule, TimeSpan tolerance, Func<DateTime> currentTimeFunction)
{
if (schedule == null) throw new ArgumentNullException(nameof(schedule));
Exception error = null;
var fetch = true;
return async cancellationToken =>
{
if (error != null)
throw new InvalidOperationException(null, error);
if (schedule == null)
return null;
while (true)
{
cancellationToken.ThrowIfCancellationRequested();
if (fetch)
{
bool moved;
try
{
moved = schedule.MoveNext();
}
catch (Exception e)
{
schedule.Dispose();
schedule = null;
error = e;
throw;
}
if (!moved)
{
schedule.Dispose();
schedule = null;
return null;
}
}
else
{
fetch = true;
}
var time = schedule.Current;
var duration = time - (currentTimeFunction?.Invoke() ?? DateTime.Now);
if (duration.Ticks > 0)
{
try
{
await Task.Delay(duration, cancellationToken).ConfigureAwait(false);
}
catch (OperationCanceledException)
{
fetch = false;
throw;
}
}
else if (duration.Ticks < -Math.Abs(tolerance.Ticks))
{
continue; // occurs in the past so loop around for next
}
return time;
}
};
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment