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; /// <summary> /// Сервис для фонового выполнения периодической работы /// </summary> 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<WorkPeriodic> works = new(8); private bool isRuning = false; /// <summary> /// Список периодических работ /// </summary> public IEnumerable<WorkPeriodic> Works => works; /// <summary> /// Работа выполняемая в данный момент /// </summary> public Work? CurrentWork; /// <summary> /// Ошибка в главном цикле, никогда не должна появляться /// </summary> 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; } /// <summary> /// Добавить фоновую работу выполняющуюся с заданным периодом /// </summary> /// <typeparam name="T"></typeparam> /// <param name="period"></param> public void Add<T>(TimeSpan period) where T : Work, new() { var work = new T(); Add(work, period); } /// <summary> /// Добавить фоновую работу выполняющуюся с заданным периодом /// </summary> /// <param name="work"></param> /// <param name="period"></param> 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; } }