Skip to content

Instantly share code, notes, and snippets.

@MysteryDash
Created March 2, 2024 00:44
Show Gist options
  • Save MysteryDash/f432846e1f62e01477b3929051563292 to your computer and use it in GitHub Desktop.
Save MysteryDash/f432846e1f62e01477b3929051563292 to your computer and use it in GitHub Desktop.
Facilities to help a generic host terminate once all BackgroundServices are done with their work.
/// <summary>
/// Provides lifetime management for background services, ensuring proper cleanup and termination.
/// </summary>
/// <remarks>This is context aware and will only activate if it detects the host is not a web application.</remarks>
/// <param name="lifetime">The application lifetime manager.</param>
/// <param name="scopeFactory">The factory for creating service scopes.</param>
public class BackgroundServiceLifetime(IHostApplicationLifetime lifetime, IServiceScopeFactory scopeFactory) : BackgroundService
{
private readonly IHostApplicationLifetime _lifetime = lifetime; // Application lifetime manager.
private readonly IServiceScopeFactory _scopeFactory = scopeFactory; // Factory for creating service scopes.
/// <summary>
/// Executes the background service asynchronously.
/// </summary>
/// <param name="stoppingToken">The token to monitor for cancellation requests.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
await using var scope = _scopeFactory.CreateAsyncScope(); // Create a new service scope.
if (scope.ServiceProvider.GetService<IWebHostEnvironment>() is not null)
return;
var services = scope
.ServiceProvider
.GetServices<IHostedService>()
.Where(m => m is BackgroundService && m != this)
.Cast<BackgroundService>(); // Retrieve all hosted background services.
// Wait until all background services are initialized.
// Not many ways to be reactive here, since there are no notifications.
while (services.Any(m => m is { ExecuteTask: null }))
await Task.Delay(TimeSpan.FromMilliseconds(10), stoppingToken);
// Wait for tasks of all other background services.
foreach (var service in services.Select(m => m.ExecuteTask))
await service!.ConfigureAwait(ConfigureAwaitOptions.SuppressThrowing);
_lifetime.StopApplication(); // Stop the application gracefully once all background services are done.
}
}
public static class ServiceCollectionExtensions
{
/// <summary>
/// Add an <see cref="BackgroundServiceLifetime"/> registration that monitors other background services and
/// stops the application when no <see cref="BackgroundService"/> is running.
/// </summary>
/// <param name="services"></param>
/// <returns></returns>
public static IServiceCollection AddBackgroundServiceLifetime(this IServiceCollection services)
{
return services.AddHostedService<BackgroundServiceLifetime>();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment