AnoiAsyncDebouncer 1.1.0
dotnet add package AnoiAsyncDebouncer --version 1.1.0
NuGet\Install-Package AnoiAsyncDebouncer -Version 1.1.0
<PackageReference Include="AnoiAsyncDebouncer" Version="1.1.0" />
<PackageVersion Include="AnoiAsyncDebouncer" Version="1.1.0" />
<PackageReference Include="AnoiAsyncDebouncer" />
paket add AnoiAsyncDebouncer --version 1.1.0
#r "nuget: AnoiAsyncDebouncer, 1.1.0"
#:package AnoiAsyncDebouncer@1.1.0
#addin nuget:?package=AnoiAsyncDebouncer&version=1.1.0
#tool nuget:?package=AnoiAsyncDebouncer&version=1.1.0
AnoiAsyncDebouncer
A lightweight, high-performance async debouncer for .NET with typed return values and cancellation token support.
Features
- Typed return values - Generic
AsyncDebouncer<T>supports any return type - Cancellation support - Proper cancellation token propagation
- Thread-safe - Safe for concurrent access
- Flush support - Execute pending operations immediately
- Max wait time - Optional cap on total debounce duration to prevent starvation under sustained input
- Memory efficient - Uses object pooling for CancellationTokenSource instances
- Zero dependencies - Only depends on .NET Standard 2.1
- Non-generic version -
AsyncDebouncerfor void operations
Installation
dotnet add package AnoiAsyncDebouncer
Or via Package Manager:
Install-Package AnoiAsyncDebouncer
Quick Start
Basic Usage (with return value)
using AnoiAsyncDebouncer;
// Create a debouncer for string results
var debouncer = new AsyncDebouncer<string>();
// Debounce an async operation with 300ms delay
var result = await debouncer.DebounceAsync(async cancellationToken =>
{
return await SearchApiAsync(searchTerm, cancellationToken);
}, delayMilliseconds: 300);
Void Operations
// For operations without return values
var debouncer = new AsyncDebouncer();
await debouncer.DebounceAsync(async cancellationToken =>
{
await SaveDataAsync(cancellationToken);
}, delayMilliseconds: 500);
Blazor Search Example
@inject HttpClient Http
@implements IDisposable
<input @oninput="OnSearchInput" placeholder="Search..." />
@code {
private readonly AsyncDebouncer<List<string>> _searchDebouncer = new();
private List<string> searchResults = new();
private async Task OnSearchInput(ChangeEventArgs e)
{
var searchTerm = e.Value?.ToString() ?? "";
searchResults = await _searchDebouncer.DebounceAsync(async ct =>
{
if (string.IsNullOrWhiteSpace(searchTerm))
return new List<string>();
return await Http.GetFromJsonAsync<List<string>>(
$"api/search?q={searchTerm}", ct) ?? new List<string>();
}, 300, defaultValue: new List<string>());
StateHasChanged();
}
public void Dispose() => _searchDebouncer.Dispose();
}
API Reference
AsyncDebouncer<T>
| Method | Description |
|---|---|
DebounceAsync(operation, delayMs, maxWaitTime?) |
Debounce an async operation. Throws TaskCanceledException if superseded. Optional maxWaitTime forces execution when debouncing has lasted longer than the specified TimeSpan. |
DebounceAsync(operation, delayMs, defaultValue, maxWaitTime?) |
Debounce with a default value returned on cancellation (no exception). |
FlushAsync() |
Execute pending operation immediately. Returns default(T) if none pending. |
Cancel() |
Cancel any pending operation. |
Dispose() |
Clean up resources and cancel pending operations. |
AsyncDebouncer (non-generic)
Same API as above but for void operations. Does not throw on cancellation.
Advanced Usage
Cancellation Support
The cancellation token passed to your operation is automatically cancelled when:
- A new debounce call supersedes the current one
Cancel()is calledDispose()is called
await debouncer.DebounceAsync(async ct =>
{
// Check cancellation in long-running operations
ct.ThrowIfCancellationRequested();
// Or pass it to async methods
await LongRunningOperationAsync(ct);
return result;
}, 300);
Flush Pending Operations
// Execute any pending operation immediately (e.g., before form submit)
var result = await debouncer.FlushAsync();
Max Wait Time (Prevent Starvation)
When a debouncer is continuously called (e.g., a stream of rapid events), normal debouncing will keep resetting the timer and the operation may never execute. The optional maxWaitTime parameter sets an upper bound on how long debouncing can continue before the operation is forced to execute.
// Force execution if debouncing has lasted more than 2 seconds,
// even if new calls keep arriving
var result = await debouncer.DebounceAsync(async ct =>
{
return await RefreshScreenAsync(ct);
}, delayMilliseconds: 300, maxWaitTime: TimeSpan.FromSeconds(2));
This is especially useful for UI scenarios such as screen redraws or live previews where you want to wait for a pause in activity, but also guarantee the user sees an update within a reasonable time.
// Non-generic version works the same way
var debouncer = new AsyncDebouncer();
await debouncer.DebounceAsync(async ct =>
{
await RenderPreviewAsync(ct);
}, delayMilliseconds: 500, maxWaitTime: TimeSpan.FromSeconds(3));
If maxWaitTime is not specified (or null), the debouncer behaves exactly as before — it will keep resetting the timer on every call.
Default Values on Cancellation
// Returns empty list instead of throwing when cancelled
var results = await debouncer.DebounceAsync(
async ct => await FetchDataAsync(ct),
delayMilliseconds: 300,
defaultValue: new List<Item>()
);
Best Practices
- Always dispose - Implement
IDisposablein your components and dispose the debouncer - Use cancellation tokens - Pass the token to your async operations for proper cancellation
- Consider default values - Use the
defaultValueoverload in UI scenarios to avoid exception handling - One debouncer per operation type - Create separate debouncers for different operations
Requirements
- .NET Standard 2.1 or higher
- Compatible with: .NET Core 3.0+, .NET 5+, .NET 6+, .NET 7+, .NET 8+
License
This project is licensed under the MIT License - see the LICENSE file for details.
Contributing
Contributions are welcome! Please feel to submit a Pull Request.
Repository
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. 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. |
| .NET Core | netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.1 is compatible. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.1
- 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.