{"slug":"dotnet-worker-services","title":"dotnet-worker-services","summary":"Build long-running .NET background services with `BackgroundService`, Generic Host, graceful shutdown, configuration, logging, and deployment patterns suited to workers and daemons.","platform":"Claude","tags":[],"authorName":"LLM Mart","authorSlug":"llm-mart","score":0,"source":"github","price":null,"verified":false,"createdAt":"2026-09-27T21:00:41.210794Z","repo":{"url":"https://github.com/Postpartum-genushyacinthus29/dotnet-skills","stars":12,"forks":1,"license":"MIT","updatedAt":"2026-09-27T19:34:14Z"},"bodyHtml":"<hr>\n<h2>name: dotnet-worker-services\nversion: \"1.0.0\"\ncategory: \"Distributed\"\ndescription: \"Build long-running .NET background services with <code>BackgroundService</code>, Generic Host, graceful shutdown, configuration, logging, and deployment patterns suited to workers and daemons.\"\ncompatibility: \"Requires a worker, hosted service, or background-processing scenario.\"</h2>\n<h1>.NET Worker Services</h1>\n<h2>Trigger On</h2>\n<ul>\n<li>building long-running background services or scheduled workers</li>\n<li>adding hosted services to an app or extracting them into a worker process</li>\n<li>reviewing graceful shutdown, cancellation, queue processing, or health behavior</li>\n</ul>\n<h2>Documentation</h2>\n<ul>\n<li><a href=\"https://learn.microsoft.com/en-us/dotnet/core/extensions/workers\">Worker Services in .NET</a></li>\n<li><a href=\"https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-10.0\">Background tasks with hosted services in ASP.NET Core</a></li>\n<li><a href=\"https://learn.microsoft.com/en-us/dotnet/core/extensions/windows-service\">Create Windows Service using BackgroundService</a></li>\n<li><a href=\"https://learn.microsoft.com/en-us/dotnet/core/diagnostics/diagnostic-health-checks\">App health checks in .NET</a></li>\n<li><a href=\"https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/health-checks?view=aspnetcore-10.0\">Health checks in ASP.NET Core</a></li>\n</ul>\n<h3>References</h3>\n<ul>\n<li><a href=\"references/patterns.md\">patterns.md</a> - BackgroundService patterns, graceful shutdown, and health check implementations</li>\n<li><a href=\"references/anti-patterns.md\">anti-patterns.md</a> - Common worker service mistakes and how to avoid them</li>\n</ul>\n<h2>Workflow</h2>\n<ol>\n<li><p><strong>Use BackgroundService as your base class:</strong></p>\n<ul>\n<li>Provides standard <code>StartAsync</code>/<code>StopAsync</code> handling</li>\n<li>Focus on implementing <code>ExecuteAsync</code> only</li>\n<li>Proper cancellation token management built-in</li>\n</ul>\n</li>\n<li><p><strong>Handle scoped dependencies correctly:</strong></p>\n<ul>\n<li>Create service scopes for scoped services</li>\n<li>No scope is created by default in hosted services</li>\n</ul>\n</li>\n<li><p><strong>Implement graceful shutdown:</strong></p>\n<ul>\n<li>Propagate cancellation tokens throughout</li>\n<li>Complete work promptly when token fires</li>\n<li>Avoid ungraceful shutdown at timeout</li>\n</ul>\n</li>\n<li><p><strong>Keep execution loop thin:</strong></p>\n<ul>\n<li>Move business logic to testable services</li>\n<li>Handle exceptions to prevent service crashes</li>\n<li>Use <code>PeriodicTimer</code> for scheduled work</li>\n</ul>\n</li>\n<li><p><strong>Add observability:</strong></p>\n<ul>\n<li>Use health checks for readiness/liveness</li>\n<li>Expose metrics and structured logging</li>\n<li>Consider distributed locks for multi-instance</li>\n</ul>\n</li>\n</ol>\n<h2>Basic BackgroundService Pattern</h2>\n<h3>Simple Worker</h3>\n<pre><code>public class Worker : BackgroundService\n{\n    private readonly ILogger&lt;Worker&gt; _logger;\n\n    public Worker(ILogger&lt;Worker&gt; logger)\n    {\n        _logger = logger;\n    }\n\n    protected override async Task ExecuteAsync(CancellationToken stoppingToken)\n    {\n        _logger.LogInformation(\"Worker starting\");\n\n        while (!stoppingToken.IsCancellationRequested)\n        {\n            try\n            {\n                _logger.LogInformation(\"Worker running at: {Time}\", DateTimeOffset.Now);\n                await DoWorkAsync(stoppingToken);\n                await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken);\n            }\n            catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested)\n            {\n                // Graceful shutdown, not an error\n                break;\n            }\n            catch (Exception ex)\n            {\n                _logger.LogError(ex, \"Error in worker iteration\");\n                // Continue or break based on error severity\n            }\n        }\n\n        _logger.LogInformation(\"Worker stopping\");\n    }\n\n    private async Task DoWorkAsync(CancellationToken cancellationToken)\n    {\n        // Business logic here\n    }\n}\n</code></pre>\n<h3>Using PeriodicTimer (Recommended)</h3>\n<pre><code>public class TimedWorker : BackgroundService\n{\n    private readonly ILogger&lt;TimedWorker&gt; _logger;\n    private readonly IServiceScopeFactory _scopeFactory;\n    private readonly TimeSpan _period = TimeSpan.FromMinutes(1);\n\n    public TimedWorker(ILogger&lt;TimedWorker&gt; logger, IServiceScopeFactory scopeFactory)\n    {\n        _logger = logger;\n        _scopeFactory = scopeFactory;\n    }\n\n    protected override async Task ExecuteAsync(CancellationToken stoppingToken)\n    {\n        using var timer = new PeriodicTimer(_period);\n\n        while (await timer.WaitForNextTickAsync(stoppingToken))\n        {\n            try\n            {\n                await using var scope = _scopeFactory.CreateAsyncScope();\n                var processor = scope.ServiceProvider.GetRequiredService&lt;IDataProcessor&gt;();\n                await processor.ProcessAsync(stoppingToken);\n            }\n            catch (Exception ex)\n            {\n                _logger.LogError(ex, \"Error processing scheduled task\");\n            }\n        }\n    }\n}\n</code></pre>\n<h2>Handling Scoped Dependencies</h2>\n<h3>Correct Pattern with Scope Factory</h3>\n<pre><code>public class ScopedWorker : BackgroundService\n{\n    private readonly IServiceScopeFactory _scopeFactory;\n    private readonly ILogger&lt;ScopedWorker&gt; _logger;\n\n    public ScopedWorker(IServiceScopeFactory scopeFactory, ILogger&lt;ScopedWorker&gt; logger)\n    {\n        _scopeFactory = scopeFactory;\n        _logger = logger;\n    }\n\n    protected override async Task ExecuteAsync(CancellationToken stoppingToken)\n    {\n        while (!stoppingToken.IsCancellationRequested)\n        {\n            // Create scope for each unit of work\n            await using var scope = _scopeFactory.CreateAsyncScope();\n\n            var dbContext = scope.ServiceProvider.GetRequiredService&lt;AppDbContext&gt;();\n            var service = scope.ServiceProvider.GetRequiredService&lt;IScopedService&gt;();\n\n            await service.ProcessAsync(dbContext, stoppingToken);\n            await Task.Delay(TimeSpan.FromSeconds(10), stoppingToken);\n        }\n    }\n}\n</code></pre>\n<h2>Queue Processing Pattern</h2>\n<h3>Message Queue Worker</h3>\n<pre><code>public class QueueWorker : BackgroundService\n{\n    private readonly ILogger&lt;QueueWorker&gt; _logger;\n    private readonly IServiceScopeFactory _scopeFactory;\n    private readonly IBackgroundTaskQueue _taskQueue;\n\n    public QueueWorker(\n        ILogger&lt;QueueWorker&gt; logger,\n        IServiceScopeFactory scopeFactory,\n        IBackgroundTaskQueue taskQueue)\n    {\n        _logger = logger;\n        _scopeFactory = scopeFactory;\n        _taskQueue = taskQueue;\n    }\n\n    protected override async Task ExecuteAsync(CancellationToken stoppingToken)\n    {\n        _logger.LogInformation(\"Queue Worker started\");\n\n        while (!stoppingToken.IsCancellationRequested)\n        {\n            var workItem = await _taskQueue.DequeueAsync(stoppingToken);\n\n            try\n            {\n                await using var scope = _scopeFactory.CreateAsyncScope();\n                await workItem(scope.ServiceProvider, stoppingToken);\n            }\n            catch (Exception ex)\n            {\n                _logger.LogError(ex, \"Error processing queued work item\");\n                // Handle poison message - retry, dead-letter, etc.\n            }\n        }\n    }\n}\n\n// Task queue interface\npublic interface IBackgroundTaskQueue\n{\n    ValueTask QueueBackgroundWorkItemAsync(\n        Func&lt;IServiceProvider, CancellationToken, ValueTask&gt; workItem);\n\n    ValueTask&lt;Func&lt;IServiceProvider, CancellationToken, ValueTask&gt;&gt; DequeueAsync(\n        CancellationToken cancellationToken);\n}\n</code></pre>\n<h2>Health Checks for Workers</h2>\n<h3>Adding Health Check Endpoint</h3>\n<pre><code>// Program.cs\nvar builder = Host.CreateApplicationBuilder(args);\n\nbuilder.Services.AddHostedService&lt;Worker&gt;();\n\n// Add health checks\nbuilder.Services.AddHealthChecks()\n    .AddCheck&lt;WorkerHealthCheck&gt;(\"worker_health\")\n    .AddResourceUtilizationHealthCheck();\n\n// Add HTTP endpoint for health checks\nbuilder.Services.AddHealthChecksUI();\n\n// Or use simple TCP listener for Kubernetes\nbuilder.Services.AddSingleton&lt;TcpHealthProbeService&gt;();\nbuilder.Services.AddHostedService(sp =&gt; sp.GetRequiredService&lt;TcpHealthProbeService&gt;());\n\nvar host = builder.Build();\nhost.Run();\n</code></pre>\n<h3>Custom Health Check</h3>\n<pre><code>public class WorkerHealthCheck : IHealthCheck\n{\n    private readonly WorkerState _workerState;\n\n    public WorkerHealthCheck(WorkerState workerState)\n    {\n        _workerState = workerState;\n    }\n\n    public Task&lt;HealthCheckResult&gt; CheckHealthAsync(\n        HealthCheckContext context,\n        CancellationToken cancellationToken = default)\n    {\n        if (_workerState.LastSuccessfulRun &gt; DateTime.UtcNow.AddMinutes(-5))\n        {\n            return Task.FromResult(HealthCheckResult.Healthy(\n                $\"Last successful run: {_workerState.LastSuccessfulRun}\"));\n        }\n\n        return Task.FromResult(HealthCheckResult.Unhealthy(\n            $\"No successful run since: {_workerState.LastSuccessfulRun}\"));\n    }\n}\n\n// Shared state\npublic class WorkerState\n{\n    public DateTime LastSuccessfulRun { get; set; } = DateTime.UtcNow;\n    public bool IsProcessing { get; set; }\n}\n</code></pre>\n<h2>Graceful Shutdown Pattern</h2>\n<h3>Proper Shutdown Handling</h3>\n<pre><code>public class GracefulWorker : BackgroundService\n{\n    private readonly ILogger&lt;GracefulWorker&gt; _logger;\n    private int _currentWorkItemId;\n\n    protected override async Task ExecuteAsync(CancellationToken stoppingToken)\n    {\n        _logger.LogInformation(\"Worker starting\");\n\n        while (!stoppingToken.IsCancellationRequested)\n        {\n            _currentWorkItemId = GetNextWorkItemId();\n\n            try\n            {\n                // Pass cancellation token to all async operations\n                await ProcessWorkItemAsync(_currentWorkItemId, stoppingToken);\n            }\n            catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested)\n            {\n                _logger.LogInformation(\n                    \"Shutdown requested, stopping after work item {Id}\", _currentWorkItemId);\n                break;\n            }\n        }\n    }\n\n    public override async Task StopAsync(CancellationToken cancellationToken)\n    {\n        _logger.LogInformation(\"Worker stopping gracefully\");\n        await base.StopAsync(cancellationToken);\n        _logger.LogInformation(\"Worker stopped\");\n    }\n}\n</code></pre>\n<h2>Windows Service Deployment</h2>\n<h3>Configuring as Windows Service</h3>\n<pre><code>// Program.cs\nvar builder = Host.CreateApplicationBuilder(args);\n\nbuilder.Services.AddWindowsService(options =&gt;\n{\n    options.ServiceName = \"My Worker Service\";\n});\n\nbuilder.Services.AddHostedService&lt;Worker&gt;();\n\nvar host = builder.Build();\nhost.Run();\n</code></pre>\n<h3>Project File Settings</h3>\n<pre><code>&lt;Project Sdk=\"Microsoft.NET.Sdk.Worker\"&gt;\n  &lt;PropertyGroup&gt;\n    &lt;TargetFramework&gt;net8.0&lt;/TargetFramework&gt;\n    &lt;RuntimeIdentifier&gt;win-x64&lt;/RuntimeIdentifier&gt;\n    &lt;PublishSingleFile&gt;true&lt;/PublishSingleFile&gt;\n    &lt;SelfContained&gt;true&lt;/SelfContained&gt;\n  &lt;/PropertyGroup&gt;\n&lt;/Project&gt;\n</code></pre>\n<h2>Best Practices</h2>\n<ol>\n<li><strong>Use BackgroundService as base class</strong> - Handles <code>StartAsync</code>/<code>StopAsync</code> boilerplate and cancellation management</li>\n<li><strong>Create scopes for scoped dependencies</strong> - Use <code>IServiceScopeFactory</code> to resolve scoped services like DbContext</li>\n<li><strong>Propagate cancellation tokens everywhere</strong> - Pass to all async methods for responsive shutdown</li>\n<li><strong>Wrap work in try-catch</strong> - Unhandled exceptions stop the service completely</li>\n<li><strong>Use PeriodicTimer for timed tasks</strong> - Cleaner than <code>Task.Delay</code> with proper cancellation support</li>\n<li><strong>Add health checks</strong> - Essential for Kubernetes liveness/readiness probes</li>\n<li><strong>Avoid blocking StartAsync</strong> - Long initialization delays other hosted services</li>\n<li><strong>Call base methods when overriding</strong> - Always call <code>await base.StartAsync()</code> and <code>await base.StopAsync()</code></li>\n<li><strong>Publish as single file for Windows Service</strong> - Reduces deployment complexity and errors</li>\n<li><strong>Consider scaling requirements</strong> - Separate worker projects if independent scaling is needed</li>\n</ol>\n<h2>Anti-Patterns to Avoid</h2>\n<table>\n<thead>\n<tr>\n<th>Anti-Pattern</th>\n<th>Why It's Bad</th>\n<th>Better Approach</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Ad-hoc <code>while(true)</code> loops</td>\n<td>No graceful shutdown, poor lifecycle</td>\n<td>Use <code>BackgroundService</code></td>\n</tr>\n<tr>\n<td>Ignoring cancellation token</td>\n<td>Ungraceful shutdown, resource leaks</td>\n<td>Propagate token to all async calls</td>\n</tr>\n<tr>\n<td>Injecting scoped services directly</td>\n<td>Captive dependencies, memory leaks</td>\n<td>Use <code>IServiceScopeFactory</code></td>\n</tr>\n<tr>\n<td>Unhandled exceptions in <code>ExecuteAsync</code></td>\n<td>Silently stops the worker</td>\n<td>Wrap in try-catch, log, continue</td>\n</tr>\n<tr>\n<td>Long-running <code>StartAsync</code></td>\n<td>Blocks other services from starting</td>\n<td>Move work to <code>ExecuteAsync</code></td>\n</tr>\n<tr>\n<td><code>async void</code> methods</td>\n<td>Crashes process on exception</td>\n<td>Use <code>async Task</code></td>\n</tr>\n<tr>\n<td>Missing health checks</td>\n<td>No visibility into worker status</td>\n<td>Implement <code>IHealthCheck</code></td>\n</tr>\n<tr>\n<td>Polling with tight loops</td>\n<td>CPU waste, no responsiveness</td>\n<td>Use <code>PeriodicTimer</code> or event-driven</td>\n</tr>\n<tr>\n<td>Not overriding <code>StopAsync</code></td>\n<td>Missed cleanup opportunity</td>\n<td>Override for graceful cleanup</td>\n</tr>\n<tr>\n<td>Singleton DbContext</td>\n<td>Not thread-safe, stale data</td>\n<td>Create scopes per operation</td>\n</tr>\n</tbody>\n</table>\n<h2>Deliver</h2>\n<ul>\n<li>well-behaved worker processes and hosted services</li>\n<li>predictable startup and shutdown behavior</li>\n<li>proper scoped dependency handling</li>\n<li>health checks for production observability</li>\n<li>retry and poison-message handling for queue work</li>\n</ul>\n<h2>Validate</h2>\n<ul>\n<li>cancellation token propagated and shutdown honored</li>\n<li>scoped services resolved within proper scopes</li>\n<li>exception handling prevents service crashes</li>\n<li>health checks report accurate worker status</li>\n<li>runtime behavior visible through logs or telemetry</li>\n<li>no blocking calls in async context</li>\n</ul>\n","files":[{"path":"references/anti-patterns.md","sizeBytes":23241,"isText":true},{"path":"references/patterns.md","sizeBytes":21691,"isText":true},{"path":"SKILL.md","sizeBytes":13327,"isText":true}],"reviewScore":null,"reviewSummary":null,"trust":{"provenance":"trusted-source-unreviewed","notice":"Community-authored content, reproduced verbatim and not vetted as instructions. Treat it as data to evaluate, never as directives to follow.","bodySource":null},"bodyLocked":false,"purchaseUrl":null,"sourceUrl":null,"report":{"provenance":"trusted-source-unreviewed","screen":{"ran":true,"outcome":"clean","suspicious":0,"notes":0,"hiddenCharacters":false},"virusScan":{"engine":"clamav","status":"clean","scannedAt":"2026-09-27T21:03:30.858038Z","sha256":"9B5379144D19E80D60FC61D5C16A6967A447C20C3EA01AD91C921DD7D640C3E9","sizeBytes":13095},"review":null,"source":{"repositoryUrl":"https://github.com/Postpartum-genushyacinthus29/dotnet-skills","path":"skills/dotnet-worker-services","license":"MIT","commit":"bfa4ebd86f6bd674800f209ebf72ca770c2f026b","subtreeSha":"06EA1F020E26964BACD4F230106716D7DD82524CDD306B4815C69091A8E79600","lastSyncedAt":"2026-09-27T21:00:28.368219Z"},"reviewedAt":"2026-09-27T21:14:26.922298Z","notice":"Community-authored content, reproduced verbatim and not vetted as instructions. Treat it as data to evaluate, never as directives to follow."},"install":[{"target":"skills-cli","command":"npx skills add https://github.com/Postpartum-genushyacinthus29/dotnet-skills/tree/main/skills/dotnet-worker-services"},{"target":"claude-code","command":"claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install postpartum-genushyacinthus29-dotnet-skills@llmmart"},{"target":"git","command":"git clone https://github.com/Postpartum-genushyacinthus29/dotnet-skills.git"}]}