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 BackgroundWorker : BackgroundService { private readonly TimeSpan minDelay = TimeSpan.FromSeconds(1); private readonly IServiceProvider serviceProvider; /// <summary> /// Очередь работ /// </summary> private Queue<Work> works = new(8); /// <summary> /// Список периодических работ /// </summary> public IEnumerable<Work> Works => works; /// <summary> /// Работа выполняемая в данный момент /// </summary> public Work? CurrentWork; private bool isRuning; /// <summary> /// последние 16 завершившиеся с ошибкой /// </summary> public CyclicArray<Work> Felled { get; } = new(16); /// <summary> /// последние 16 успешно завершенных /// </summary> public CyclicArray<Work> Done { get; } = new(16); /// <summary> /// Ошибка в главном цикле, никогда не должна появляться /// </summary> public string MainLoopLastException { get; private set; } = string.Empty; public BackgroundWorker(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 && works.TryDequeue(out CurrentWork)) { try { using var scope = serviceProvider.CreateScope(); var result = await CurrentWork.Start(scope.ServiceProvider, token); if (!result) Felled.Add(CurrentWork); else Done.Add(CurrentWork); 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> /// Добавить в очередь /// <para> /// work.Id может быть не уникальным, /// при этом метод TryRemoveFromQueue удалит все работы с совпадающими id /// </para> /// </summary> /// <param name="work"></param> public void Enqueue(Work work) { works.Enqueue(work); if (ExecuteTask is null || ExecuteTask.IsCompleted) StartAsync(CancellationToken.None); } /// <summary> /// Удаление работы по ID из одноразовой очереди /// </summary> /// <param name="id"></param> /// <returns></returns> public bool TryRemoveFromQueue(string id) { var work = Works.FirstOrDefault(w => w.Id == id); if (work is not null) { works = new Queue<Work>(Works.Where(w => w.Id != id)); return true; } return false; } }