| File: BackgroundService.cs | Web Access |
| Project: src\runtime\src\libraries\Microsoft.Extensions.Hosting.Abstractions\src\Microsoft.Extensions.Hosting.Abstractions.csproj (Microsoft.Extensions.Hosting.Abstractions) |
// 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); // Execute all of ExecuteAsync asynchronously, and store the task we're executing so that we can wait for it later. _executeTask = Task.Run(() => ExecuteAsync(_stoppingCts.Token), _stoppingCts.Token); // Always return a completed task. Any result from ExecuteAsync will be handled by the Host. 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 NET 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(); } } }