asynchronous programming
task scheduling
timers
software development
programming techniques

Proper way to implement a never ending task. Timers vs Task

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

Long-running background work is common in servers, agents, and desktop applications, but a "never ending task" is easy to implement badly. The real requirements are usually simple: run work repeatedly, avoid overlapping executions, support shutdown, and surface failures. In modern C#, a cooperative async loop is usually the right default, while raw timers are better reserved for simpler callback-style work.

Why Timers Often Cause Trouble

A timer looks attractive because it is short to write:

csharp
1using System;
2using System.Threading;
3
4var timer = new Timer(_ =>
5{
6    Console.WriteLine($"Tick at {DateTime.UtcNow:O}");
7}, null, TimeSpan.Zero, TimeSpan.FromSeconds(5));
8
9Console.ReadLine();
10timer.Dispose();

This works for trivial callbacks, but it has a major weakness: the timer does not care whether the previous callback has finished. If the work takes longer than the interval, callbacks can overlap and compete for the same resources.

That is fine for lightweight metrics or heartbeat updates. It is much less fine for polling a queue, writing files, or calling an external API where concurrency must be controlled.

Prefer An Async Loop For Repeated Work

If the job has real business logic, a dedicated async loop is easier to reason about. You wait, run one iteration, handle errors, and honor cancellation.

In an ASP.NET Core service, BackgroundService plus PeriodicTimer is a strong pattern:

csharp
1using System;
2using System.Threading;
3using System.Threading.Tasks;
4using Microsoft.Extensions.Hosting;
5using Microsoft.Extensions.Logging;
6
7public sealed class PollingWorker : BackgroundService
8{
9    private readonly ILogger<PollingWorker> _logger;
10
11    public PollingWorker(ILogger<PollingWorker> logger)
12    {
13        _logger = logger;
14    }
15
16    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
17    {
18        using var timer = new PeriodicTimer(TimeSpan.FromSeconds(10));
19
20        while (await timer.WaitForNextTickAsync(stoppingToken))
21        {
22            try
23            {
24                await PollAsync(stoppingToken);
25            }
26            catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested)
27            {
28                break;
29            }
30            catch (Exception ex)
31            {
32                _logger.LogError(ex, "Polling iteration failed");
33            }
34        }
35    }
36
37    private static async Task PollAsync(CancellationToken cancellationToken)
38    {
39        Console.WriteLine($"Polling at {DateTime.UtcNow:O}");
40        await Task.Delay(1500, cancellationToken);
41    }
42}

This design gives you exactly one active iteration at a time. It also makes shutdown predictable because the loop listens to the cancellation token.

When A Plain Task Loop Is Enough

If you are not inside a hosted application, you can still use the same idea with a regular async method:

csharp
1using System;
2using System.Threading;
3using System.Threading.Tasks;
4
5public static async Task RunWorkerAsync(CancellationToken cancellationToken)
6{
7    while (!cancellationToken.IsCancellationRequested)
8    {
9        try
10        {
11            await DoWorkAsync(cancellationToken);
12        }
13        catch (Exception ex) when (ex is not OperationCanceledException)
14        {
15            Console.WriteLine(ex.Message);
16        }
17
18        await Task.Delay(TimeSpan.FromSeconds(5), cancellationToken);
19    }
20}
21
22static Task DoWorkAsync(CancellationToken cancellationToken)
23{
24    Console.WriteLine($"Work at {DateTime.UtcNow:O}");
25    return Task.CompletedTask;
26}

This is often enough for command-line tools, daemons, or integration utilities. The important part is that the delay happens after the work finishes, so iterations cannot pile up.

Choosing The Right Tool

Use a timer when the callback is small, stateless, and safe to overlap, or when you explicitly want time-based triggering regardless of callback duration.

Use an async loop when the job needs backpressure, cancellation, retries, structured logging, or one-at-a-time execution. That is the more robust default for production services.

If you need repeated scheduling with no overlap, PeriodicTimer is often a better fit than the older Timer API because it integrates naturally with async code.

Common Pitfalls

The biggest mistake is writing while (true) without a cancellation path. That makes shutdown hard and usually turns testing into a mess.

Another common problem is allowing overlapping timer callbacks. If each callback touches shared state, subtle race conditions follow quickly.

Swallowed exceptions are also dangerous. A background loop that fails silently can stop doing useful work while the process appears healthy.

Finally, avoid blocking calls such as .Wait() or .Result inside async background code. They increase the risk of deadlocks and thread starvation.

Summary

  • Raw timers are fine for simple periodic callbacks, but they can overlap.
  • An async loop is usually the better model for long-running background work.
  • 'PeriodicTimer works well with async and helps keep one iteration active at a time.'
  • Always support cancellation, logging, and exception handling.
  • Prefer clarity and predictable execution over the shortest possible implementation.

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.