feat: add background task

This commit is contained in:
Polianin Nikita 2024-10-27 05:42:50 +03:00
parent b095ca9749
commit 80e74b34c1
4 changed files with 142 additions and 3 deletions

View File

@ -0,0 +1,15 @@
using System;
namespace Mirea.Api.Endpoint.Common.Services;
public static class ScheduleSyncManager
{
public static event Action? OnUpdateIntervalRequested;
public static event Action? OnForceSyncRequested;
public static void RequestIntervalUpdate() =>
OnUpdateIntervalRequested?.Invoke();
public static void RequestForceSync() =>
OnForceSyncRequested?.Invoke();
}

View File

@ -0,0 +1,121 @@
using Cronos;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Mirea.Api.Endpoint.Common.Services;
using Mirea.Api.Endpoint.Configuration.Model;
using Mirea.Api.Endpoint.Sync;
using System;
using System.Threading;
using System.Threading.Tasks;
using IServiceProvider = System.IServiceProvider;
namespace Mirea.Api.Endpoint.Configuration.Core.BackgroundTasks;
public class ScheduleSyncService : IHostedService, IDisposable
{
private Timer? _timer;
private readonly IOptionsMonitor<GeneralConfig> _generalConfigMonitor;
private readonly ILogger<ScheduleSyncService> _logger;
private CancellationTokenSource _cancellationTokenSource = new();
private readonly IServiceProvider _serviceProvider;
public ScheduleSyncService(IOptionsMonitor<GeneralConfig> generalConfigMonitor, ILogger<ScheduleSyncService> logger, IServiceProvider serviceProvider)
{
_generalConfigMonitor = generalConfigMonitor;
_logger = logger;
_serviceProvider = serviceProvider;
ScheduleSyncManager.OnForceSyncRequested += OnForceSyncRequested;
ScheduleSyncManager.OnUpdateIntervalRequested += OnUpdateIntervalRequested;
}
private void OnForceSyncRequested()
{
StopAsync(default).ContinueWith(_ =>
{
_cancellationTokenSource = new CancellationTokenSource();
ExecuteTask(null);
});
}
private void OnUpdateIntervalRequested()
{
StopAsync(default).ContinueWith(_ =>
{
StartAsync(default);
});
}
private void ScheduleNextRun()
{
var cronExpression = _generalConfigMonitor.CurrentValue.ScheduleSettings?.CronUpdateSchedule;
if (string.IsNullOrEmpty(cronExpression))
{
_logger.LogWarning("Cron expression is not set. The scheduled task will not run.");
return;
}
var nextRunTime = CronExpression.Parse(cronExpression).GetNextOccurrence(DateTimeOffset.Now, TimeZoneInfo.Local);
if (!nextRunTime.HasValue)
{
_logger.LogWarning("No next run time found. The task will not be scheduled. Timezone: {TimeZone}", TimeZoneInfo.Local.DisplayName);
return;
}
_logger.LogInformation("Next task run in {Time}", nextRunTime.Value.ToString("G"));
var delay = (nextRunTime.Value - DateTimeOffset.Now).TotalMilliseconds;
// The chance is small, but it's better to check
if (delay <= 0)
delay = 1;
_cancellationTokenSource = new CancellationTokenSource();
_timer = new Timer(ExecuteTask, null, (int)delay, Timeout.Infinite);
}
private async void ExecuteTask(object? state)
{
try
{
using var scope = _serviceProvider.CreateScope();
var syncService = ActivatorUtilities.GetServiceOrCreateInstance<ScheduleSynchronizer>(scope.ServiceProvider);
await syncService.StartSync(_cancellationTokenSource.Token);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error occurred during schedule synchronization.");
}
finally
{
ScheduleNextRun();
}
}
public Task StartAsync(CancellationToken cancellationToken)
{
ScheduleNextRun();
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
_cancellationTokenSource.Cancel();
_timer?.Change(Timeout.Infinite, 0);
return Task.CompletedTask;
}
public void Dispose()
{
StopAsync(default).GetAwaiter().GetResult();
_timer?.Dispose();
ScheduleSyncManager.OnForceSyncRequested -= OnForceSyncRequested;
ScheduleSyncManager.OnUpdateIntervalRequested -= OnUpdateIntervalRequested;
_cancellationTokenSource.Dispose();
GC.SuppressFinalize(this);
}
}

View File

@ -5,9 +5,9 @@
<ImplicitUsings>disable</ImplicitUsings> <ImplicitUsings>disable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<Company>Winsomnia</Company> <Company>Winsomnia</Company>
<Version>1.0-rc3</Version> <Version>1.0-rc4</Version>
<AssemblyVersion>1.0.2.3</AssemblyVersion> <AssemblyVersion>1.0.2.4</AssemblyVersion>
<FileVersion>1.0.2.3</FileVersion> <FileVersion>1.0.2.4</FileVersion>
<AssemblyName>Mirea.Api.Endpoint</AssemblyName> <AssemblyName>Mirea.Api.Endpoint</AssemblyName>
<RootNamespace>$(AssemblyName)</RootNamespace> <RootNamespace>$(AssemblyName)</RootNamespace>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>

View File

@ -10,6 +10,7 @@ using Mirea.Api.DataAccess.Persistence;
using Mirea.Api.DataAccess.Persistence.Common; using Mirea.Api.DataAccess.Persistence.Common;
using Mirea.Api.Endpoint.Common.Interfaces; using Mirea.Api.Endpoint.Common.Interfaces;
using Mirea.Api.Endpoint.Common.Services; using Mirea.Api.Endpoint.Common.Services;
using Mirea.Api.Endpoint.Configuration.Core.BackgroundTasks;
using Mirea.Api.Endpoint.Configuration.Core.Middleware; using Mirea.Api.Endpoint.Configuration.Core.Middleware;
using Mirea.Api.Endpoint.Configuration.Core.Startup; using Mirea.Api.Endpoint.Configuration.Core.Startup;
using Mirea.Api.Endpoint.Configuration.Model; using Mirea.Api.Endpoint.Configuration.Model;
@ -63,6 +64,8 @@ public class Program
builder.Services.AddSingleton<IMaintenanceModeService, MaintenanceModeService>(); builder.Services.AddSingleton<IMaintenanceModeService, MaintenanceModeService>();
builder.Services.AddSingleton<ISetupToken, SetupTokenService>(); builder.Services.AddSingleton<ISetupToken, SetupTokenService>();
builder.Services.AddHostedService<ScheduleSyncService>();
builder.Services.AddMemoryCache(); builder.Services.AddMemoryCache();
builder.Services.AddCustomRedis(builder.Configuration, healthCheckBuilder); builder.Services.AddCustomRedis(builder.Configuration, healthCheckBuilder);