Skip to main content

background-services-channels

Implement IHostedService and BackgroundService with Channel-based producer/consumer patterns for async work queues.

Source facts

Repository
visionarycoder/vc.Ifx
Last source activity
September 10, 2026 at 04:35
Detected SKILL.md language
English
Stars
0
Forks
0

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
background-services-channels
title
Background Services with Channels
description
Implement IHostedService and BackgroundService with Channel-based producer/consumer patterns for async work queues.
doc_type
skill
status
active
last_updated
2026-08-29T00:00:00.000Z
target_audience
ai
complexity
medium
estimated_tokens
1350
prerequisites
["ste-agent-writing-standard","terminology-dictionary"]
related_skills
["long-running-job-patterns","api-infrastructure-bundle","message-patterns-dotnet"]
appliesTo
**/*.{cs,csproj}
tags
["aspnetcore","background-service","channels","async","hosted-service"]
# Background Services with Channels Agent implements background worker services using `IHostedService`, `BackgroundService`, and `System.Threading.Channels` for async work queues. ## When to Use | Condition | Agent Action | |---|---| | Application processes background work outside request pipeline | Use this skill | | Application uses producer/consumer pattern for async tasks | Use this skill | | Application needs graceful shutdown for long-running operations | Use this skill | ## When Not to Use | Condition | Agent Action | |---|---| | Work executes within HTTP request scope only | Use middleware or filters | | Work requires durable queue (survives restart) | Use Azure Service Bus or RabbitMQ | | Scheduled work with cron patterns | Use Quartz.NET | ## Core Patterns | Pattern | Use When | Implementation | |---|---|---| | `BackgroundService` | Single long-running loop | Override `ExecuteAsync` with `while (!stoppingToken.IsCancellationRequested)` | | `IHostedService` | Custom lifecycle control | Implement `StartAsync` and `StopAsync` | | `Channel<T>` | Producer/consumer queue | Use `Channel.CreateUnbounded<T>()` or bounded | | Multiple consumers | High throughput needed | Start multiple `BackgroundService` instances reading same channel | ## Workflow | Step | Agent Action | Test | Pass | |---|---|---|---| | 1. Create service | Agent creates class inheriting `BackgroundService`. | Inspect class declaration. | Class inherits `BackgroundService`. | | 2. Inject channel reader | Agent injects `ChannelReader<T>` into service constructor. | Inspect constructor parameters. | `ChannelReader<T>` parameter exists. | | 3. Implement loop | Agent overrides `ExecuteAsync` with cancellation-aware loop. | Inspect `ExecuteAsync` method. | Method checks `stoppingToken.IsCancellationRequested`. | | 4. Register services | Agent registers background service and channel in DI. | Inspect `Program.cs` registrations. | `AddHostedService` and channel singleton exist. | | 5. Add producer | Agent creates writer methods that write to `ChannelWriter<T>`. | Inspect producer code. | Producer uses `await writer.WriteAsync(item)`. | | 6. Test shutdown | Agent verifies graceful shutdown behavior. | Stop application during work. | Service completes current item before exit. | ## Implementation Pattern ### Channel Registration ```csharp var channel = Channel.CreateUnbounded<WorkItem>(new UnboundedChannelOptions { SingleReader = false, // Multiple consumers allowed SingleWriter = false // Multiple producers allowed }); builder.Services.AddSingleton(channel.Reader); builder.Services.AddSingleton(channel.Writer); ``` ### Background Service ```csharp public class WorkerService : BackgroundService { private readonly ChannelReader<WorkItem> _reader; private readonly ILogger<WorkerService> _logger; public WorkerService(ChannelReader<WorkItem> reader, ILogger<WorkerService> logger) { _reader = reader; _logger = logger; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { await foreach (var item in _reader.ReadAllAsync(stoppingToken)) { var result = await ProcessItemAsync(item, stoppingToken); if (result.Error is not null) { _logger.LogError(result.Error, "Error processing item {ItemId}", item.Id); } } } private async Task<WorkItemResult> ProcessItemAsync(WorkItem item, CancellationToken cancellationToken) { // Process work item await Task.Delay(100, cancellationToken); // Placeholder return new WorkItemResult(null); } } public sealed record WorkItemResult(Exception? Error); ``` ### Producer Pattern ```csharp public class WorkQueueService { private readonly ChannelWriter<WorkItem> _writer; public WorkQueueService(ChannelWriter<WorkItem> writer) => _writer = writer; public async ValueTask QueueWorkAsync(WorkItem item, CancellationToken cancellationToken = default) { await _writer.WriteAsync(item, cancellationToken); } } ``` ## Configuration Rules | Rule | Agent Verifies | Fix | |---|---|---| | BG-001 | `ExecuteAsync` checks cancellation token | Add `stoppingToken.IsCancellationRequested` checks | | BG-002 | Exception handling wraps work processing | Add an error boundary around item processing | | BG-003 | Channel capacity limits prevent unbounded growth when needed | Use `CreateBounded` with capacity limit | | BG-004 | Graceful shutdown timeout is configured | Set `HostOptions.ShutdownTimeout` | | BG-005 | Multiple consumers process in parallel when needed | Register multiple `AddHostedService<T>` instances | ## Channel Options | Option | Use When | Configuration | |---|---|---| | Unbounded | Queue size unknown, memory allows growth | `Channel.CreateUnbounded<T>()` | | Bounded with Wait | Producer blocks when full | `CreateBounded<T>(capacity, BoundedChannelFullMode.Wait)` | | Bounded with Drop | Drop oldest items when full | `BoundedChannelFullMode.DropOldest` | | Bounded with Reject | Reject new items when full | `BoundedChannelFullMode.DropWrite` | ## Graceful Shutdown Pattern ```csharp public override async Task StopAsync(CancellationToken cancellationToken) { _logger.LogInformation("Worker stopping, waiting for current work to complete"); await base.StopAsync(cancellationToken); } ``` ## Verification Checklist | Check | Test | Pass | |---|---|---| | Service starts | Start application and check logs. | Background service logs startup. | | Work processes | Queue work item and verify processing. | Item processes successfully. | | Cancellation works | Stop application during processing. | Service stops gracefully within shutdown timeout. | | Exception handling | Queue item that throws exception. | Service logs error and continues processing. | | Channel communication | Producer writes and consumer reads. | Items flow from producer to consumer. | ## Common Pitfalls | Pitfall | Agent Fix | |---|---| | Synchronous blocking in `ExecuteAsync` | Agent uses `await` throughout. | | No cancellation token checks | Agent adds `stoppingToken.IsCancellationRequested` checks. | | Unbounded channel with unlimited producers | Agent switches to bounded channel with capacity limit. | | No exception handling | Agent wraps processing in an error boundary. | | Missing graceful shutdown | Agent configures `HostOptions.ShutdownTimeout`. | ## Outputs - `BackgroundService` implementation with cancellation support - Channel-based producer/consumer registration - Graceful shutdown configuration - Exception handling and logging patterns
View on GitHub