using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; namespace AsbCloudInfrastructure.Background; /// /// Сервис для фонового выполнения периодической работы /// public class PeriodicBackgroundWorker : BackgroundService { private readonly TimeSpan executePeriod = TimeSpan.FromSeconds(10); private readonly TimeSpan minDelay = TimeSpan.FromSeconds(1); private readonly IServiceProvider serviceProvider; private readonly List works = new(8); private bool isRuning = false; /// /// Список периодических работ /// public IEnumerable Works => works; /// /// Работа выполняемая в данный момент /// public Work? CurrentWork; /// /// Ошибка в главном цикле, никогда не должна появляться /// public string MainLoopLastException { get; private set; } = string.Empty; public PeriodicBackgroundWorker(IServiceProvider serviceProvider) { this.serviceProvider = serviceProvider; } protected override async Task ExecuteAsync(CancellationToken token) { if (isRuning) return; isRuning = true; Trace.TraceInformation($"{GetType().Name} started"); while (!token.IsCancellationRequested) { try { var periodicWork = GetNext(); if (periodicWork is null) { await Task.Delay(executePeriod, token); continue; } CurrentWork = periodicWork.Work; using var scope = serviceProvider.CreateScope(); var result = await periodicWork.Work.Start(scope.ServiceProvider, token); CurrentWork = null; await Task.Delay(minDelay, token); } catch (Exception ex) { MainLoopLastException = $"BackgroundWorker " + $"MainLoopLastException: \r\n" + $"date: {DateTime.Now:O}\r\n" + $"message: {ex.Message}\r\n" + $"inner: {ex.InnerException?.Message}\r\n" + $"stackTrace: {ex.StackTrace}"; Trace.TraceError(MainLoopLastException); } } isRuning = false; } /// /// Добавить фоновую работу выполняющуюся с заданным периодом /// /// /// public void Add(TimeSpan period) where T : Work, new() { var work = new T(); Add(work, period); } /// /// Добавить фоновую работу выполняющуюся с заданным периодом /// /// /// public void Add(Work work, TimeSpan period) { var periodic = new WorkPeriodic(work, period); works.Add(periodic); if (ExecuteTask is null || ExecuteTask.IsCompleted) StartAsync(CancellationToken.None); } private WorkPeriodic? GetNext() { var work = works .OrderBy(w => w.NextStart) .FirstOrDefault(); if (work is null || work.NextStart > DateTime.Now) return null; return work; } }