Mastering the Background: Manual Control of IHostedService in ASP.NET Core
ASP.NET Core's IHostedService
interface provides a powerful way to execute background tasks within your application. However, sometimes you need more granular control over these tasks than simply starting and stopping them. This article explores how to manually start, stop, and even pause your IHostedService
implementations, giving you the flexibility to manage background processes precisely.
The Scenario: A Need for Controlled Background Tasks
Imagine you're developing an e-commerce application. You need to run a background service that periodically checks for new product updates from an external API. While you want this task to run continuously, you also need to be able to pause it during peak traffic hours to minimize server load.
Original Code:
public class ProductUpdater : IHostedService, IDisposable
{
private readonly ILogger<ProductUpdater> _logger;
public ProductUpdater(ILogger<ProductUpdater> logger)
{
_logger = logger;
}
public async Task StartAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("Product Updater service started.");
while (!cancellationToken.IsCancellationRequested)
{
await UpdateProducts();
await Task.Delay(TimeSpan.FromMinutes(10), cancellationToken);
}
}
public Task StopAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("Product Updater service stopped.");
return Task.CompletedTask;
}
private async Task UpdateProducts()
{
// Logic to fetch new product data from external API
// ...
}
public void Dispose()
{
// Clean up resources
}
}
This basic implementation will run continuously but lacks the flexibility for manual control.
Adding Manual Control: The Power of Events and a Manual Switch
To achieve manual control, we introduce a custom event to signal start/stop/pause actions and a flag to track the current state. Here's how we enhance our ProductUpdater
service:
public class ProductUpdater : IHostedService, IDisposable
{
private readonly ILogger<ProductUpdater> _logger;
private readonly IEventAggregator _eventAggregator;
private bool _isRunning = false;
private CancellationTokenSource _cts = new CancellationTokenSource();
public ProductUpdater(ILogger<ProductUpdater> logger, IEventAggregator eventAggregator)
{
_logger = logger;
_eventAggregator = eventAggregator;
_eventAggregator.GetEvent<StartProductUpdaterEvent>().Subscribe(Start);
_eventAggregator.GetEvent<StopProductUpdaterEvent>().Subscribe(Stop);
_eventAggregator.GetEvent<PauseProductUpdaterEvent>().Subscribe(Pause);
_eventAggregator.GetEvent<ResumeProductUpdaterEvent>().Subscribe(Resume);
}
public async Task StartAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("Product Updater service starting.");
_isRunning = true;
await Task.Run(() => StartService(_cts.Token), cancellationToken);
}
public async Task StopAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("Product Updater service stopping.");
_isRunning = false;
_cts.Cancel();
await Task.CompletedTask;
}
private async Task StartService(CancellationToken cancellationToken)
{
while (_isRunning && !cancellationToken.IsCancellationRequested)
{
await UpdateProducts();
await Task.Delay(TimeSpan.FromMinutes(10), cancellationToken);
}
}
private async Task StopService(CancellationToken cancellationToken)
{
_logger.LogInformation("Product Updater service stopped.");
// Implement any additional cleanup logic if needed
_isRunning = false;
_cts.Cancel();
}
private void Pause()
{
_logger.LogInformation("Product Updater service paused.");
_isRunning = false;
}
private void Resume()
{
_logger.LogInformation("Product Updater service resumed.");
_isRunning = true;
}
public void Dispose()
{
// Clean up resources
}
}
Explanation:
- Event Aggregation: We use an
IEventAggregator
(implement your own or use a library like Prism) to publish events for manual control. - Start/Stop/Pause/Resume Methods: These methods handle the corresponding actions, updating the
_isRunning
flag and canceling/resuming the internalCancellationTokenSource
. - CancellationTokenSource: We use a
CancellationTokenSource
to control the internal service loop. - Event Subscription: In the constructor, we subscribe to the events, ensuring these methods are called when relevant actions are triggered.
Triggering Manual Control:
You can now trigger the start, stop, pause, and resume actions from your application, typically from a controller or another service:
// Publish events to control the ProductUpdater service
_eventAggregator.GetEvent<StartProductUpdaterEvent>().Publish(new StartProductUpdaterEvent());
_eventAggregator.GetEvent<StopProductUpdaterEvent>().Publish(new StopProductUpdaterEvent());
_eventAggregator.GetEvent<PauseProductUpdaterEvent>().Publish(new PauseProductUpdaterEvent());
_eventAggregator.GetEvent<ResumeProductUpdaterEvent>().Publish(new ResumeProductUpdaterEvent());
Conclusion: Flexibility for Precise Background Task Management
By implementing manual control for your IHostedService
, you gain the ability to precisely manage the execution of background tasks within your ASP.NET Core application. This flexibility allows you to adjust the behavior of these services based on application state, load conditions, or user interactions. Remember to choose the appropriate approach for controlling your background services, ensuring your application runs smoothly and efficiently.