Installer avec Codex ou Claude Copiez ce prompt, collez-le dans Codex, Claude ou un autre assistant, puis laissez-le vérifier la page du skill et l'installer pour vous.
Une commande directe contourne le prompt de vérification. Examinez la source avant de l'exécuter.
{"short-description":".NET skill guidance for architecture tasks"}
dotnet-background-services
Patterns for long-running background work in .NET applications. Covers BackgroundService, IHostedService, hosted
service lifecycle, and graceful shutdown handling.
Scope
BackgroundService and IHostedService patterns
Hosted service lifecycle and startup ordering
Graceful shutdown and cancellation handling
Periodic work, polling, and queue-draining loops
Out of scope
DI registration mechanics and service lifetimes -- see [skill:dotnet-csharp-dependency-injection]
Async/await patterns and cancellation token propagation -- see [skill:dotnet-csharp-async-patterns]
Project scaffolding -- see [skill:dotnet-scaffold-project]
Testing strategies for background services -- see [skill:dotnet-testing-strategy] and
[skill:dotnet-integration-testing]
Channel fundamentals and drain patterns -- see [skill:dotnet-channels]
Cross-references: [skill:dotnet-csharp-async-patterns] for async patterns in background workers,
[skill:dotnet-csharp-dependency-injection] for hosted service registration and scope management, [skill:dotnet-channels]
for Channel patterns used in background work queues.
publicsealedclassOrderProcessorWorker(
IServiceScopeFactory scopeFactory,
ILogger<OrderProcessorWorker> logger) : BackgroundService
{
protectedoverrideasync Task ExecuteAsync(CancellationToken stoppingToken)
{
logger.LogInformation("Order processor started");
while (!stoppingToken.IsCancellationRequested)
{
try
{
usingvar scope = scopeFactory.CreateScope();
var processor = scope.ServiceProvider
.GetRequiredService<IOrderProcessor>();
var processed = await processor.ProcessPendingAsync(stoppingToken);
if (processed == 0)
{
// No work available -- back off to avoid tight pollingawait Task.Delay(TimeSpan.FromSeconds(5), stoppingToken);
}
}
catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested)
{
// Expected during shutdown -- do not log as errorbreak;
}
catch (Exception ex)
{
logger.LogError(ex, "Error processing orders");
// Back off on error to prevent tight failure loopsawait Task.Delay(TimeSpan.FromSeconds(30), stoppingToken);
}
}
logger.LogInformation("Order processor stopped");
}
}
// Registration
builder.Services.AddHostedService<OrderProcessorWorker>();
```text
**Always create scopes** -- `BackgroundService` registered a singleton. Inject `IServiceScopeFactory`,
services directly.
**Always handle exceptions** -- , unhandled exceptions `ExecuteAsync`
{
{
logger.LogInformation();
scope = scopeFactory.CreateScope();
cache = scope.ServiceProvider.GetRequiredService<IProductCache>();
cache.WarmAsync(cancellationToken);
logger.LogInformation();
}
=> Task.CompletedTask;
}
```text
```
{
IConnection? _connection;
{
logger.LogInformation();
_connection = CreateConnectionAsync(cancellationToken);
}
{
logger.LogInformation();
(_connection )
{
_connection.CloseAsync(cancellationToken);
_connection = ;
}
}
{
NotImplementedException();
}
}
```text
---
Understanding the startup shutdown sequence critical correct behavior.
`IHostedService.StartAsync` called each registered service ** registration order**
`BackgroundService.ExecuteAsync` called after `StartAsync` completes (it runs concurrently -- the host does
wait it to finish)
The host ready to serve requests after all `StartAsync` calls complete
**Important:** `ExecuteAsync` must block before yielding to the caller. The first `` `ExecuteAsync`
control returns to the host. If you have synchronous setup before the first ``, keep it move it to
`StartAsync` via an .
```csharp
:
{
{
InitializeAsync(cancellationToken);
.StartAsync(cancellationToken);
}
{
(!stoppingToken.IsCancellationRequested)
{
DoWorkAsync(stoppingToken);
}
}
=> Task.CompletedTask;
=> Task.CompletedTask;
}
```text
`IHostApplicationLifetime.ApplicationStopping` triggered
The host calls `StopAsync` each hosted service ** reverse registration order**
For `BackgroundService`, the stopping token cancelled, then `StopAsync` waits `ExecuteAsync` to complete
`IHostApplicationLifetime.ApplicationStopped` triggered
---
See [skill:dotnet-channels] comprehensive `Channel<T>` guidance including bounded/unbounded options,
`BoundedChannelFullMode`, backpressure strategies, `itemDropped` callbacks, multiple consumers, performance tuning,
drain patterns.
The most common integration a channel-backed background task queue consumed a `BackgroundService`:
```csharp
{
Channel<Func<IServiceProvider, CancellationToken, Task>> _queue
= Channel.CreateBounded<Func<IServiceProvider, CancellationToken, Task>>(
BoundedChannelOptions() { FullMode = BoundedChannelFullMode.Wait });
ChannelWriter<Func<IServiceProvider, CancellationToken, Task>> Writer => _queue.Writer;
ChannelReader<Func<IServiceProvider, CancellationToken, Task>> Reader => _queue.Reader;
}
{
{
( queue.Reader.WaitToReadAsync(stoppingToken))
{
(queue.Reader.TryRead( workItem))
{
{
scope = scopeFactory.CreateScope();
workItem(scope.ServiceProvider, stoppingToken);
}
(Exception ex)
{
logger.LogError(ex, );
}
}
}
}
}
builder.Services.AddSingleton<BackgroundTaskQueue>();
builder.Services.AddHostedService<QueueProcessorWorker>();
```text
---
By , the host waits seconds services to stop. Configure -running operations:
```csharp
builder.Services.Configure<HostOptions>(options =>
{
options.ShutdownTimeout = TimeSpan.FromSeconds();
});
```text
```
{
{
lifetime.ApplicationStarted.Register(() =>
logger.LogInformation());
lifetime.ApplicationStopping.Register(() =>
logger.LogInformation());
lifetime.ApplicationStopped.Register(() =>
logger.LogInformation());
Task.CompletedTask;
}
=> Task.CompletedTask;
}
```text
---
Use `PeriodicTimer` instead of `Task.Delay` more accurate periodic execution:
```
{
{
timer = PeriodicTimer(TimeSpan.FromMinutes());
( timer.WaitForNextTickAsync(stoppingToken))
{
{
scope = scopeFactory.CreateScope();
reporter = scope.ServiceProvider
.GetRequiredService<IHealthReporter>();
reporter.ReportAsync(stoppingToken);
}
(Exception ex)
{
logger.LogError(ex, );
}
}
}
}
```text
---
**Do inject services BackgroundService constructors** -- they are singletons. Always use
`IServiceScopeFactory`.
**Do use `Task.Run` background work** -- use `BackgroundService` proper lifecycle management graceful
shutdown.
**Do swallow `OperationCanceledException`** -- it propagate re-check the stopping token. Swallowing it
prevents graceful shutdown.
**Do use `Thread.Sleep`** -- use ` Task.Delay(duration, stoppingToken)` `PeriodicTimer`.
**Do forget to register** -- `AddHostedService<T>()` ; merely implementing the .
---
## ( )
** :** :
1. ** **: ``
2. ** **: ``
3. ** **: ``
4. ** **: ``
** :**
- ✅ ** **: , , ,
- ✅ ** /**: , ,
- ✅ ****: ,
** :**
```
# :
: //
: " "
# :
: "/"
: "//"
```
##
- [ ](:
- [](:
- [ ](:
- [ ](:
- [](:
### Critical Rules for BackgroundService
1.
is
as
not
scoped
2.
by
default
in
stop the host (configurable via
`HostOptions.BackgroundServiceExceptionBehavior`). Wrap the loop body intry/catch.
3. **Always respect the stopping token** -- check `stoppingToken.IsCancellationRequested` and pass the token to all
async calls.
4. **Back off on empty/error** -- avoid tight polling loops that waste CPU. Use `Task.Delay` with the stopping token.
---
## IHostedService Patterns
### Startup Hook (Cache Warming, Migrations)
```csharp
publicsealedclassCacheWarmupService(
IServiceScopeFactory scopeFactory,
ILogger<CacheWarmupService> logger) : IHostedService