Philiprehberger.Polling 0.2.5

dotnet add package Philiprehberger.Polling --version 0.2.5
                    
NuGet\Install-Package Philiprehberger.Polling -Version 0.2.5
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Philiprehberger.Polling" Version="0.2.5" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Philiprehberger.Polling" Version="0.2.5" />
                    
Directory.Packages.props
<PackageReference Include="Philiprehberger.Polling" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Philiprehberger.Polling --version 0.2.5
                    
#r "nuget: Philiprehberger.Polling, 0.2.5"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Philiprehberger.Polling@0.2.5
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Philiprehberger.Polling&version=0.2.5
                    
Install as a Cake Addin
#tool nuget:?package=Philiprehberger.Polling&version=0.2.5
                    
Install as a Cake Tool

Philiprehberger.Polling

CI NuGet Last updated

Poll any async operation until a condition is met with configurable intervals, timeouts, and backoff strategies.

Installation

dotnet add package Philiprehberger.Polling

Usage

using Philiprehberger.Polling;

// Poll an API until the order status is "completed"
var result = await Poll
    .Until(
        async () => await client.GetOrderStatusAsync(orderId),
        status => status == "completed")
    .Every(TimeSpan.FromSeconds(2))
    .WithTimeout(TimeSpan.FromMinutes(5))
    .WithBackoff(BackoffStrategy.Exponential)
    .ExecuteAsync();

if (result.Succeeded)
    Console.WriteLine($"Order completed after {result.Attempts} attempts");

Side-effect polling

Poll an operation that throws until it succeeds:

var result = await Poll
    .Until(async () => await db.PingAsync())
    .Every(TimeSpan.FromSeconds(1))
    .WithTimeout(TimeSpan.FromSeconds(30))
    .ExecuteAsync();

Backoff strategies

// Constant (default) — same interval every attempt
.WithBackoff(BackoffStrategy.Constant)

// Linear — interval grows by base each attempt (500ms, 1s, 1.5s, ...)
.WithBackoff(BackoffStrategy.Linear)

// Exponential — interval doubles each attempt (500ms, 1s, 2s, 4s, ...)
.WithBackoff(BackoffStrategy.Exponential)

// Exponential with jitter — exponential + random jitter to avoid thundering herd
.WithBackoff(BackoffStrategy.ExponentialWithJitter)

Max Attempts

Limit polling to a fixed number of attempts, independent of timeout:

var result = await Poll
    .Until(
        async () => await client.GetJobStatusAsync(jobId),
        status => status == "done")
    .Every(TimeSpan.FromSeconds(1))
    .WithMaxAttempts(10)
    .ExecuteAsync();

if (!result.Succeeded)
    Console.WriteLine($"Gave up after {result.Attempts} attempts");

Polling Context

Use a context-aware predicate to access attempt metadata:

var result = await Poll
    .Until(
        async () => await GetValueAsync(),
        (value, context) =>
        {
            Console.WriteLine($"Attempt {context.AttemptNumber}, elapsed {context.Elapsed}");
            return value > 100;
        })
    .Every(TimeSpan.FromSeconds(1))
    .ExecuteAsync();

The PollContext provides AttemptNumber (1-based), Elapsed time, and LastException.

Exception Filtering

Only retry on specific exception types; other exceptions propagate immediately:

var result = await Poll
    .Until(async () => await db.PingAsync())
    .Every(TimeSpan.FromSeconds(1))
    .OnlyRetryOn<TimeoutException>()
    .WithTimeout(TimeSpan.FromSeconds(30))
    .ExecuteAsync();

Observability

var result = await Poll
    .Until(
        async () => await service.GetHealthAsync(),
        health => health.IsHealthy)
    .Every(TimeSpan.FromSeconds(1))
    .OnAttempt((value, attempt) =>
        Console.WriteLine($"Attempt {attempt}: healthy={value.IsHealthy}"))
    .ExecuteAsync();

Cancellation

using var cts = new CancellationTokenSource();

var result = await Poll
    .Until(
        async () => await GetValueAsync(),
        v => v > 100)
    .Every(TimeSpan.FromMilliseconds(200))
    .WithCancellation(cts.Token)
    .ExecuteAsync();

API

Poll

Method Description
Until<T>(Func<Task<T>>, Func<T, bool>) Create a poll builder that checks a predicate against returned values
Until<T>(Func<Task<T>>, Func<T, PollContext, bool>) Create a poll builder with a context-aware predicate
Until(Func<Task>) Create a poll builder for a side-effect operation that succeeds when it stops throwing

PollBuilder<T> / PollBuilder

Method Description
Every(TimeSpan) Set the base interval between attempts (default 500 ms)
WithTimeout(TimeSpan) Set the maximum total polling duration
WithMaxAttempts(int) Set the maximum number of polling attempts
Until(Func<T, PollContext, bool>) Set a context-aware predicate (PollBuilder<T> only)
WithBackoff(BackoffStrategy) Set the backoff strategy (default Constant)
OnlyRetryOn<TException>() Only retry on the specified exception type
OnAttempt(Action<T, int>) Register a callback after each attempt
WithCancellation(CancellationToken) Provide a cancellation token
ExecuteAsync() Run the poll loop and return a PollResult<T>

PollContext

Property Type Description
AttemptNumber int Number of attempts made so far (1-based)
Elapsed TimeSpan Total time elapsed since polling started
LastException Exception? The last exception encountered, if any

PollResult<T>

Property Type Description
Succeeded bool Whether the predicate was satisfied
Value T? The last value returned by the operation
Attempts int Total number of attempts executed
Elapsed TimeSpan Total wall-clock time spent polling
LastException Exception? The last exception thrown, if any
IsTimedOut bool Whether polling ended due to timeout

BackoffStrategy

Value Description
Constant Same interval every attempt
Linear Interval grows by base each attempt
Exponential Interval doubles each attempt
ExponentialWithJitter Exponential with random jitter

Development

dotnet build src/Philiprehberger.Polling.csproj --configuration Release

Support

If you find this project useful:

Star the repo

🐛 Report issues

💡 Suggest features

❤️ Sponsor development

🌐 All Open Source Projects

💻 GitHub Profile

🔗 LinkedIn Profile

License

MIT

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net8.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.2.5 119 4/1/2026
0.2.4 109 3/27/2026
0.2.3 105 3/25/2026
0.2.2 104 3/23/2026
0.2.1 112 3/17/2026
0.2.0 113 3/17/2026
0.1.3 117 3/16/2026
0.1.2 109 3/16/2026
0.1.1 114 3/16/2026