[CHANGE] BackgroundServices

This commit is contained in:
max
2025-09-09 19:11:02 +02:00
parent d183803390
commit 2593d02a73
7 changed files with 188 additions and 17 deletions

View File

@@ -26,8 +26,8 @@ public static class DependencyInjection
});
builder.Services.AddSingleton<HostedServiceConnector>();
builder.Services.AddHostedService<ClientManager>();
builder.Services.AddSingleton<BackgroundServiceManager>();
builder.Services.AddHostedService<ClientService>();
builder.Services.AddScoped<ILibraryService, LibraryService>();
}

View File

@@ -0,0 +1,56 @@
using System.Threading.Channels;
namespace Manager.App.Services;
public class CircularBuffer <T>
{
private readonly T[] _buffer;
private readonly Channel<T> _channel;
public int Capacity { get; }
public int Head { get; private set; }
public int Count { get; private set; }
public CircularBuffer(int capacity)
{
if (capacity <= 0)
{
throw new ArgumentOutOfRangeException(nameof(capacity));
}
Capacity = capacity;
_buffer = new T[Capacity];
_channel = Channel.CreateUnbounded<T>(new UnboundedChannelOptions
{
SingleReader = false,
SingleWriter = false,
});
}
public void Add(T item)
{
_buffer[Head] = item;
Head = (Head + 1) % _buffer.Length;
if (Count < _buffer.Length)
{
Count++;
}
_channel.Writer.TryWrite(item);
}
public IEnumerable<T> Items
{
get
{
for (var i = 0; i < Count; i++)
{
yield return _buffer[(Head - Count + i + _buffer.Length) % _buffer.Length];
}
}
}
public IAsyncEnumerable<T> GetStreamAsync() => _channel.Reader.ReadAllAsync();
}

View File

@@ -0,0 +1,97 @@
using DotBased.Logging;
using Manager.App.Services.System;
using ILogger = Microsoft.Extensions.Logging.ILogger;
namespace Manager.App.Services;
public abstract class ExtendedBackgroundService : BackgroundService
{
private readonly ManualResetEventSlim _resetEvent = new(true);
private readonly ILogger _logger;
public ServiceState State { get; private set; } = ServiceState.Stopped;
public CircularBuffer<ServiceProgress> ProgressLog { get; } = new(500);
public string Name { get; }
public TimeSpan ExecuteInterval { get; set; }
public ExtendedBackgroundService(string name, ILogger logger, BackgroundServiceManager manager, TimeSpan? executeInterval = null)
{
Name = name;
_logger = logger;
manager.RegisterService(this);
ExecuteInterval = executeInterval ?? TimeSpan.Zero;
}
protected sealed override async Task ExecuteAsync(CancellationToken stoppingToken)
{
State = ServiceState.Running;
_logger.LogInformation("Starting background service: {ServiceName}", Name);
try
{
while (!stoppingToken.IsCancellationRequested)
{
_resetEvent.Wait(stoppingToken);
await Task.Delay(ExecuteInterval, stoppingToken);
await ExecuteServiceAsync(stoppingToken);
}
}
catch (Exception e)
{
if (e is not OperationCanceledException)
{
State = ServiceState.Faulted;
_logger.LogError(e,"Background service {ServiceName} faulted!", Name);
throw;
}
_logger.LogInformation(e,"Service {ServiceName} received cancellation", Name);
}
finally
{
State = ServiceState.Stopped;
}
}
protected void LogProgress(string message, LogSeverity severity = LogSeverity.Info) => ProgressLog.Add(new ServiceProgress(message, DateTime.UtcNow, severity));
public void Pause()
{
if (State == ServiceState.Running)
{
_resetEvent.Reset();
State = ServiceState.Paused;
_logger.LogInformation("Pauses service: {ServiceName}", Name);
}
}
public void Resume()
{
if (State == ServiceState.Paused)
{
_resetEvent.Set();
State = ServiceState.Running;
_logger.LogInformation("Resumes service: {ServiceName}", Name);
}
}
protected abstract Task ExecuteServiceAsync(CancellationToken stoppingToken);
public override bool Equals(object? obj)
{
return obj is ExtendedBackgroundService bgService && bgService.Name.Equals(Name, StringComparison.OrdinalIgnoreCase);
}
public override int GetHashCode()
{
return Name.GetHashCode();
}
}
public enum ServiceState
{
Stopped,
Faulted,
Running,
Paused
}
public record ServiceProgress(string Message, DateTime StartTime, LogSeverity Severity);

View File

@@ -0,0 +1,23 @@
using Manager.App.Models.System;
namespace Manager.App.Services.System;
public class BackgroundServiceManager
{
private readonly HashSet<ExtendedBackgroundService> _backgroundServices = [];
public void RegisterService(ExtendedBackgroundService service)
{
_backgroundServices.Add(service);
}
public ListResult<ExtendedBackgroundService> GetServices(string serviceName, int total = 20, int skip = 0)
{
var filtered = string.IsNullOrWhiteSpace(serviceName) ? _backgroundServices.ToArray() : _backgroundServices.Where(x => x.Name.Equals(serviceName, StringComparison.OrdinalIgnoreCase)).ToArray();
var results = filtered.OrderDescending()
.Skip(skip)
.Take(total);
return new ListResultReturn<ExtendedBackgroundService>(results.ToList(), filtered.Length);
}
}

View File

@@ -6,19 +6,19 @@ using Manager.YouTube;
namespace Manager.App.Services.System;
public class ClientManager(IServiceScopeFactory scopeFactory, HostedServiceConnector serviceConnector) : BackgroundService
public class ClientService(IServiceScopeFactory scopeFactory, ILogger<ClientService> logger, BackgroundServiceManager serviceManager) : ExtendedBackgroundService("ClientService", logger, serviceManager)
{
private readonly List<YouTubeClient> _clients = [];
private CancellationToken _cancellationToken;
private ILibraryService? _libraryService;
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
protected override async Task ExecuteServiceAsync(CancellationToken stoppingToken)
{
serviceConnector.RegisterService(this);
_cancellationToken = stoppingToken;
stoppingToken.Register(CancellationRequested);
using var scope = scopeFactory.CreateScope();
_libraryService = scope.ServiceProvider.GetRequiredService<ILibraryService>();
LogProgress("Initializing service...");
}
private void CancellationRequested()

View File

@@ -1,11 +0,0 @@
namespace Manager.App.Services.System;
public class HostedServiceConnector
{
private readonly List<IHostedService> _hostedServices = [];
public void RegisterService(IHostedService service)
{
_hostedServices.Add(service);
}
}