|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.Extensions.Hosting
{
/// <summary>
/// Base class for implementing a long running <see cref="IHostedService"/>.
/// </summary>
public abstract class BackgroundService : IHostedService, IDisposable
{
private Task? _executeTask;
private CancellationTokenSource? _stoppingCts;
/// <summary>
/// Gets the Task that executes the background operation.
/// </summary>
/// <remarks>
/// Will return <see langword="null"/> if the background operation hasn't started.
/// </remarks>
public virtual Task? ExecuteTask => _executeTask;
/// <summary>
/// This method is called when the <see cref="IHostedService"/> starts. The implementation should return a task that represents
/// the lifetime of the long running operation(s) being performed.
/// </summary>
/// <param name="stoppingToken">Triggered when <see cref="IHostedService.StopAsync(CancellationToken)"/> is called.</param>
/// <returns>A <see cref="Task"/> that represents the long running operations.</returns>
/// <remarks>See <see href="https://learn.microsoft.com/dotnet/core/extensions/workers">Worker Services in .NET</see> for implementation guidelines.</remarks>
protected abstract Task ExecuteAsync(CancellationToken stoppingToken);
/// <summary>
/// Triggered when the application host is ready to start the service.
/// </summary>
/// <param name="cancellationToken">Indicates that the start process has been aborted.</param>
/// <returns>A <see cref="Task"/> that represents the asynchronous Start operation.</returns>
public virtual Task StartAsync(CancellationToken cancellationToken)
{
// Create linked token to allow cancelling executing task from provided token
_stoppingCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
// Store the task we're executing
_executeTask = ExecuteAsync(_stoppingCts.Token);
// If the task is completed then return it, this will bubble cancellation and failure to the caller
if (_executeTask.IsCompleted)
{
return _executeTask;
}
// Otherwise it's running
return Task.CompletedTask;
}
/// <summary>
/// Triggered when the application host is performing a graceful shutdown.
/// </summary>
/// <param name="cancellationToken">Indicates that the shutdown process should no longer be graceful.</param>
/// <returns>A <see cref="Task"/> that represents the asynchronous Stop operation.</returns>
public virtual async Task StopAsync(CancellationToken cancellationToken)
{
// Stop called without start
if (_executeTask == null)
{
return;
}
try
{
// Signal cancellation to the executing method
_stoppingCts!.Cancel();
}
finally
{
#if NET8_0_OR_GREATER
await _executeTask.WaitAsync(cancellationToken).ConfigureAwait(ConfigureAwaitOptions.SuppressThrowing);
#else
// Wait until the task completes or the stop token triggers
var tcs = new TaskCompletionSource<object>();
using CancellationTokenRegistration registration = cancellationToken.Register(s => ((TaskCompletionSource<object>)s!).SetCanceled(), tcs);
// Do not await the _executeTask because cancelling it will throw an OperationCanceledException which we are explicitly ignoring
await Task.WhenAny(_executeTask, tcs.Task).ConfigureAwait(false);
#endif
}
}
/// <inheritdoc />
public virtual void Dispose()
{
_stoppingCts?.Cancel();
}
}
}
|