|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Linq;
|
|
|
|
|
using System.Text;
|
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
|
|
|
|
|
namespace Scheduler.Backgrounding
|
|
|
|
|
{
|
|
|
|
|
public class BGTaskLoop
|
|
|
|
|
{
|
|
|
|
|
public Guid Guid { get; } = Guid.NewGuid();
|
|
|
|
|
private int _intervalID = 0;
|
|
|
|
|
|
|
|
|
|
private readonly CancellationTokenSource _cts = new();
|
|
|
|
|
private readonly IBGTaskLoopable _bgTaskDelegate;
|
|
|
|
|
private readonly PeriodicTimer _pTimer;
|
|
|
|
|
private Task? _bgTask;
|
|
|
|
|
|
|
|
|
|
public event EventHandler<BGTaskStartEvent>? OnExecuteStart;
|
|
|
|
|
public event EventHandler<BGTaskEndEvent>? OnExecuteEnd;
|
|
|
|
|
|
|
|
|
|
public BGTaskLoop(IBGTaskLoopable task, TimeSpan interval)
|
|
|
|
|
{
|
|
|
|
|
_bgTaskDelegate = task;
|
|
|
|
|
_pTimer = new PeriodicTimer(interval);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Start()
|
|
|
|
|
{
|
|
|
|
|
_bgTask = RunBGTaskOnInterval();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async Task RunBGTaskOnInterval()
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
while (await _pTimer.WaitForNextTickAsync(_cts.Token)) //TODO: Maybe inform the scheduling core when
|
|
|
|
|
//task time > interval
|
|
|
|
|
//or automatically update interval?
|
|
|
|
|
//"BackgroundTaskScheduler that will call "update interval" etc.
|
|
|
|
|
{
|
|
|
|
|
int id = ++_intervalID;
|
|
|
|
|
OnExecuteStart?.Invoke(this, new(Guid, id, DateTime.Now));
|
|
|
|
|
await _bgTaskDelegate.OnBGTaskLoopAsync();
|
|
|
|
|
OnExecuteEnd?.Invoke(this, new(Guid, id, DateTime.Now));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async Task StopAsync()
|
|
|
|
|
{
|
|
|
|
|
if (_bgTask == null)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
_cts.Cancel();
|
|
|
|
|
await _bgTask; //ensure completion
|
|
|
|
|
_pTimer.Dispose();
|
|
|
|
|
_cts.Dispose();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public class BGTaskLoop<TResult>
|
|
|
|
|
{
|
|
|
|
|
public Guid Guid { get; } = Guid.NewGuid();
|
|
|
|
|
private int _intervalID = 0;
|
|
|
|
|
|
|
|
|
|
private readonly CancellationTokenSource _cts = new();
|
|
|
|
|
private readonly IBGTaskLoopable<TResult> _bgTaskDelegate;
|
|
|
|
|
private PeriodicTimer _pTimer;
|
|
|
|
|
private TimeSpan _currentIntrvl;
|
|
|
|
|
private Task? _bgTask;
|
|
|
|
|
|
|
|
|
|
public event EventHandler<BGTaskStartEvent>? OnExecuteStart;
|
|
|
|
|
public event EventHandler<BGTaskEndEvent<TResult>>? OnExecuteEnd;
|
|
|
|
|
|
|
|
|
|
public BGTaskLoop(IBGTaskLoopable<TResult> task, TimeSpan interval)
|
|
|
|
|
{
|
|
|
|
|
_bgTaskDelegate = task;
|
|
|
|
|
_currentIntrvl = interval;
|
|
|
|
|
_pTimer = new PeriodicTimer(interval);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Start()
|
|
|
|
|
{
|
|
|
|
|
_bgTask = RunBGTaskOnIntervalAsync();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
internal void Start(TimeSpan startAfter)
|
|
|
|
|
{
|
|
|
|
|
_bgTask = RunBGTaskOnIntervalAsync(startAfter);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async Task RunBGTaskOnIntervalAsync(TimeSpan? startAfter = null)
|
|
|
|
|
{
|
|
|
|
|
if (startAfter.HasValue)
|
|
|
|
|
await Task.Delay((int)startAfter.Value.TotalMilliseconds);
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
while (await _pTimer.WaitForNextTickAsync(_cts.Token)) //TODO: Maybe inform the scheduling core when
|
|
|
|
|
//task time > interval
|
|
|
|
|
//or automatically update interval?
|
|
|
|
|
//"BackgroundTaskScheduler that will call "update interval" etc.
|
|
|
|
|
{
|
|
|
|
|
bool update = _updateRequested;
|
|
|
|
|
if (update)
|
|
|
|
|
{
|
|
|
|
|
_pTimer.Dispose(); //Dispose directly after call,
|
|
|
|
|
//.WaitForNextTickAsync(..) will now return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int id = ++_intervalID;
|
|
|
|
|
OnExecuteStart?.Invoke(this, new(Guid, id, DateTime.Now));
|
|
|
|
|
TResult result = await _bgTaskDelegate.OnBGTaskLoopAsync();
|
|
|
|
|
OnExecuteEnd?.Invoke(this, new(Guid, id, DateTime.Now, result));
|
|
|
|
|
|
|
|
|
|
if (update)
|
|
|
|
|
{
|
|
|
|
|
UpdateRunBGTaskOnInterval();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
var asd = ex;
|
|
|
|
|
//TODO: Invoke OnError?
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private bool _updateRequested = false;
|
|
|
|
|
//TODO: make it instantly act upon timer,
|
|
|
|
|
//eg. if timer would trigger 2 sec after .UpdateInterval(..)
|
|
|
|
|
//and newInterval is lower than 2 sec
|
|
|
|
|
//make the call earlier somehow :-)
|
|
|
|
|
//opt: extract while business logic to method and call it earlier than while loop wouldve done
|
|
|
|
|
/// <summary> Updates the current timer loop to return on a new interval. This will take effect after any current iteration is in process</summary>
|
|
|
|
|
public void UpdateInterval(TimeSpan newInterval)
|
|
|
|
|
{
|
|
|
|
|
_currentIntrvl = newInterval;
|
|
|
|
|
_updateRequested = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void UpdateRunBGTaskOnInterval()
|
|
|
|
|
{
|
|
|
|
|
_updateRequested = false;
|
|
|
|
|
_pTimer = new(_currentIntrvl);
|
|
|
|
|
_bgTask = RunBGTaskOnIntervalAsync();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async Task StopAsync()
|
|
|
|
|
{
|
|
|
|
|
if (_bgTask == null)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
_cts.Cancel();
|
|
|
|
|
await _bgTask; //ensure completion
|
|
|
|
|
_pTimer.Dispose();
|
|
|
|
|
_cts.Dispose();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|