Asynchronous programming
polling techniques
task scheduling
continuous polling
software development

Continuous polling using Tasks

Master System Design with Codemia

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

Introduction

Continuous polling with Task in C# means running a repeating asynchronous loop that checks some external condition at intervals without blocking the main thread. The important part is not just putting code in a while (true) loop. A good polling loop needs delay control, cancellation, exception handling, and a clear rule for what happens when work takes longer than the polling interval.

Start With an Async Polling Loop

A minimal polling loop uses Task.Delay and a CancellationToken.

csharp
1using System;
2using System.Net.Http;
3using System.Threading;
4using System.Threading.Tasks;
5
6public static class Poller
7{
8    public static async Task RunAsync(CancellationToken cancellationToken)
9    {
10        using var client = new HttpClient();
11
12        while (!cancellationToken.IsCancellationRequested)
13        {
14            string result = await client.GetStringAsync("https://example.com", cancellationToken);
15            Console.WriteLine($"Received {result.Length} characters at {DateTime.UtcNow:O}");
16
17            await Task.Delay(TimeSpan.FromSeconds(5), cancellationToken);
18        }
19    }
20}

This is already far better than a busy loop because it gives the thread back to the runtime between polling attempts.

Always Support Cancellation

Polling that cannot stop cleanly is usually a bug. Use a CancellationTokenSource from the caller and pass its token into both the poll work and the delay.

csharp
1using var cts = new CancellationTokenSource();
2Task pollingTask = Poller.RunAsync(cts.Token);
3
4Console.ReadLine();
5cts.Cancel();
6
7await pollingTask;

This pattern matters in services, desktop apps, and test environments. If the application is shutting down, you want the loop to exit promptly rather than waiting for an arbitrary amount of time.

Handle Exceptions Inside the Loop Deliberately

A continuous poller eventually encounters network failures, timeouts, or invalid responses. If you do not handle those exceptions intentionally, the task may fault and stop polling forever.

csharp
1public static async Task RunAsync(CancellationToken cancellationToken)
2{
3    using var client = new HttpClient();
4
5    while (!cancellationToken.IsCancellationRequested)
6    {
7        try
8        {
9            string result = await client.GetStringAsync("https://example.com", cancellationToken);
10            Console.WriteLine(result);
11        }
12        catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
13        {
14            break;
15        }
16        catch (Exception ex)
17        {
18            Console.WriteLine($"Polling failed: {ex.Message}");
19        }
20
21        await Task.Delay(TimeSpan.FromSeconds(5), cancellationToken);
22    }
23}

This keeps temporary failures from killing the background worker permanently.

Decide Whether Polling Intervals Are Fixed or Sliding

There are two common timing models:

  • wait five seconds after each poll completes
  • start a new poll every five seconds on a fixed schedule

The Task.Delay pattern shown above produces a sliding interval. If the HTTP request takes two seconds and the delay is five seconds, the next poll begins about seven seconds after the last one started.

That is often fine. If you need stricter timing, PeriodicTimer is cleaner in modern .NET.

csharp
1using System;
2using System.Threading;
3using System.Threading.Tasks;
4
5public static async Task RunPeriodicAsync(CancellationToken cancellationToken)
6{
7    using var timer = new PeriodicTimer(TimeSpan.FromSeconds(5));
8
9    while (await timer.WaitForNextTickAsync(cancellationToken))
10    {
11        Console.WriteLine($"Polling at {DateTime.UtcNow:O}");
12    }
13}

Use the tool that matches your timing requirement instead of assuming every polling loop behaves the same way.

Avoid Fire-and-Forget Task Piles

A frequent mistake is starting new tasks on every tick without awaiting them. That can create overlapping polls, race conditions, or an unbounded backlog if the remote system is slow.

In other words, avoid patterns like “start a task every second no matter what” unless overlapping work is explicitly intended and controlled.

A safe default is sequential polling: each iteration waits for the previous one to finish before the next begins.

Keep the Polling Work Small and Focused

The polling loop should usually do three things only:

  1. fetch or inspect the external state
  2. process the result quickly
  3. wait until the next cycle or stop

If heavy business logic lives inside the polling loop, testing and error handling become much harder. Push complicated processing into separate methods or services and keep the loop readable.

Common Pitfalls

A common mistake is writing a while (true) loop with no Task.Delay. That creates a hot loop that wastes CPU and can overwhelm downstream systems.

Another issue is ignoring cancellation. If the app tries to shut down and the poller never cooperates, background tasks can hang the process.

Developers also sometimes let exceptions escape the loop, which silently kills the task after the first transient failure.

Finally, be careful with overlapping polls. If the work can take longer than the interval, decide explicitly whether sequential execution or concurrent execution is the correct behavior.

Summary

  • Continuous polling with Task should be asynchronous, delayed, and cancellable.
  • 'Task.Delay is a good default for a sliding polling interval.'
  • 'PeriodicTimer is useful when you want a clearer periodic schedule in modern .NET.'
  • Catch transient exceptions so one failure does not end the loop permanently.
  • Avoid tight loops and uncontrolled fire-and-forget task creation.

Course illustration
Course illustration

All Rights Reserved.