windows service
periodic tasks
scheduled tasks
background services
automation

Windows Service that runs Periodically

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

A Windows Service that runs periodically is a good fit when you need an always-installed background process that wakes up on a schedule, performs work, and keeps running without user interaction. In modern .NET, the cleanest way to build one is usually a Worker Service hosted as a Windows Service.

That said, not every repeating task should be a service. If the job runs once per hour or once per day and does not need a continuously running host process, Windows Task Scheduler is often simpler. A service makes sense when the process must stay resident, react quickly, or expose long-running operational behavior.

A Modern Pattern: Worker Service Plus BackgroundService

Current .NET guidance favors IHostedService or BackgroundService for long-running background processes. When you add Windows Service hosting, the same worker can run under the Service Control Manager instead of as a console app.

The Program.cs file is small:

csharp
1using Microsoft.Extensions.DependencyInjection;
2using Microsoft.Extensions.Hosting;
3
4HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
5builder.Services.AddWindowsService(options =>
6{
7    options.ServiceName = "PeriodicReportService";
8});
9builder.Services.AddHostedService<PeriodicWorker>();
10
11IHost host = builder.Build();
12host.Run();

The worker itself can use PeriodicTimer to execute at fixed intervals without the reentrancy problems that many timer callbacks introduce:

csharp
1using Microsoft.Extensions.Hosting;
2using Microsoft.Extensions.Logging;
3
4public sealed class PeriodicWorker : BackgroundService
5{
6    private readonly ILogger<PeriodicWorker> _logger;
7
8    public PeriodicWorker(ILogger<PeriodicWorker> logger)
9    {
10        _logger = logger;
11    }
12
13    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
14    {
15        using var timer = new PeriodicTimer(TimeSpan.FromMinutes(5));
16
17        while (!stoppingToken.IsCancellationRequested &&
18               await timer.WaitForNextTickAsync(stoppingToken))
19        {
20            try
21            {
22                _logger.LogInformation("Running periodic job at {Time}", DateTimeOffset.Now);
23                await DoWorkAsync(stoppingToken);
24            }
25            catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested)
26            {
27                break;
28            }
29            catch (Exception ex)
30            {
31                _logger.LogError(ex, "Periodic job failed");
32            }
33        }
34    }
35
36    private static async Task DoWorkAsync(CancellationToken cancellationToken)
37    {
38        await Task.Delay(1000, cancellationToken);
39    }
40}

This is a solid starting point for polling, file processing, cache refresh, or scheduled integration work.

Why PeriodicTimer Is Better Than a Raw Timer Callback

Older Windows Service examples often use System.Timers.Timer. That can work, but it is easy to accidentally trigger overlapping executions if the timer fires again before the previous run finishes.

PeriodicTimer makes the control flow clearer because your loop waits for the next tick only after the current iteration has completed. That is a much safer default for jobs that must not overlap, such as billing runs or file imports.

It also integrates naturally with async code and cancellation tokens, which matters when the service is asked to stop gracefully.

Deployment and Operational Considerations

A periodic service is not only code. You also need to think about how it runs in production.

Key decisions include:

  • which Windows account the service runs under
  • where logs are written
  • what should happen after a failure
  • whether missed executions need catch-up behavior

For installation and lifetime management, you can publish the worker and register it as a Windows Service with the usual service tooling or deployment automation. During development, it is often easiest to run the same code as a console app first so logs and exceptions are visible immediately.

Common Pitfalls

The first pitfall is choosing a Windows Service for a job that should really be a scheduled task. If the process only needs to start occasionally, a permanent service may be unnecessary operational overhead.

Another common problem is overlapping runs. Raw timers can fire again while the previous invocation is still running. That produces duplicate work, lock contention, and hard-to-debug race conditions.

Unhandled exceptions are also dangerous. A background worker should catch, log, and classify failures so one bad iteration does not crash the whole service without explanation.

Service account permissions are another frequent issue. The code may work locally under your developer identity and then fail in production because the service account cannot read a folder, connect to SQL Server, or write logs.

Finally, do not hardcode the interval. Put it in configuration so operations can tune the cadence without rebuilding the application.

Summary

  • Use a Windows Service when you need a resident background process, not just a daily timer.
  • In modern .NET, BackgroundService and Windows Service hosting are the cleanest starting point.
  • 'PeriodicTimer is a safer periodic loop than many older callback-based timer patterns.'
  • Catch and log failures inside the loop so the service remains diagnosable.
  • Validate service-account permissions early in deployment.
  • If the task is infrequent and self-contained, consider Task Scheduler instead of a service.

Course illustration
Course illustration

All Rights Reserved.