Created
March 2, 2024 00:44
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// <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