Last active
September 15, 2016 11:29
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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