using System; using System.Linq; using System.Threading; using System.Threading.Tasks; using Serilog; using ShareeBike.Repository.Exception; namespace ShareeBike.ViewModel { /// /// Object to periodically actuate an action. /// public class PollingUpdateTask { /// Object to control canceling. private CancellationTokenSource CancellationTokenSource { get; } /// Task to perform update. private Task UpdateTask { get; } /// Action which performs an update. private Action UpdateAction { get; } /// Objects which does a periodic update. /// Update action to perform. /// Holds whether to poll or not and the period length is polling is on. public PollingUpdateTask( Action updateAction, TimeSpan? polling) { UpdateAction = updateAction ?? throw new ArgumentException($"Can not construct {GetType().Name}- object. Argument {nameof(updateAction)} must not be null."); if (!polling.HasValue) { // Automatic update is switched off. Log.ForContext().Debug($"Automatic update is off, context {GetType().Name} at {DateTime.Now}."); return; } var updatePeriodeSet = polling.Value; CancellationTokenSource = new CancellationTokenSource(); int cycleIndex = 2; UpdateTask = Task.Run( async () => { while (!CancellationTokenSource.IsCancellationRequested) { await Task.Delay(updatePeriodeSet, CancellationTokenSource.Token); { // N. update cycle Log.ForContext().Information($"Actuating {cycleIndex} update cycle, context {GetType().Name} at {DateTime.Now}."); UpdateAction(); Log.ForContext().Debug($"Update cycle {cycleIndex}, context {GetType().Name} finished at {DateTime.Now}."); cycleIndex = cycleIndex < int.MaxValue ? ++cycleIndex : 0; } } }, CancellationTokenSource.Token); } /// /// Invoked when pages is closed/ hidden. /// Stops update process. /// public async Task Terminate() { if (UpdateTask == null) { // Nothing to do if no task was created. return; } // Cancel update task; if (CancellationTokenSource == null) { throw new Exception($"Can not terminate periodical update task, context {GetType().Name} at {DateTime.Now}. No task running."); } Log.ForContext().Information($"Request to terminate update cycle, context {GetType().Name} at {DateTime.Now}."); CancellationTokenSource.Cancel(); try { await UpdateTask; } catch (TaskCanceledException) { // Polling update was canceled. // Nothing to notice/ worry about. } catch (Exception exception) { // An error occurred updating pin. var aggregateException = exception as AggregateException; if (aggregateException == null) { // Unexpected exception detected. Exception should always be of type AggregateException Log.Error("An/ several errors occurred on update task. {@Exceptions}.", exception); } else { if (aggregateException.InnerExceptions.Count == 1 && aggregateException.InnerExceptions[0].GetType() == typeof(TaskCanceledException)) { // Polling update was canceled. // Nothing to notice/ worry about. Log.ForContext().Debug($"Update cycle terminated."); } else if (aggregateException.InnerExceptions.Count() > 0 && aggregateException.InnerExceptions.First(x => !x.GetIsConnectFailureException()) == null) // There is no exception which is not of type connect failure. { // All exceptions were caused by communication error Log.Information("An/ several web related connect failure exceptions occurred on update task. {@Exceptions}.", exception); } else { // All exceptions were caused by communication errors. Log.Error("An/ several exceptions occurred on update task. {@Exception}.", exception); } } } Log.ForContext().Debug($"Update cycle terminated (not canceled)."); } } }