Debouncer.Sharp 1.1.6

There is a newer version of this package available.
See the version list below for details.
dotnet add package Debouncer.Sharp --version 1.1.6
                    
NuGet\Install-Package Debouncer.Sharp -Version 1.1.6
                    
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="Debouncer.Sharp" Version="1.1.6" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Debouncer.Sharp" Version="1.1.6" />
                    
Directory.Packages.props
<PackageReference Include="Debouncer.Sharp" />
                    
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 Debouncer.Sharp --version 1.1.6
                    
#r "nuget: Debouncer.Sharp, 1.1.6"
                    
#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 Debouncer.Sharp@1.1.6
                    
#: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=Debouncer.Sharp&version=1.1.6
                    
Install as a Cake Addin
#tool nuget:?package=Debouncer.Sharp&version=1.1.6
                    
Install as a Cake Tool

Debouncer.Sharp

Thread-safe debounce primitives for delayed, coalesced async work.

Three tiers, pick the one that matches your use case:

Tier Answers Example
Action (IDebouncer) "Run this after a quiet period, cancel-and-replace on every hit." Search-as-you-type, UI refresh, validation
Latest (IDebouncedLatest<T>) "Keep only the newest value, apply it once after a quiet period." Autosave, settings persistence, snapshot writes
Batch (IDebouncedBatch<T>) "Collect everything, flush the batch after a quiet period — but never delay forever under continuous load." Telemetry, logs, audit trails, burst writes

Install

<PackageReference Include="Debouncer.Sharp" Version="1.0.*" />

Or via CLI:

dotnet add package Debouncer.Sharp

Versioning follows Semantic Versioning. Releases are cut by pushing a vX.Y.Z git tag; see CHANGELOG.md for release history.

Releasing

The Publish NuGet workflow only runs on vX.Y.Z tag pushes, and will fail unless the .csproj version and CHANGELOG.md are both updated first. To cut a release:

  1. Bump <Version> in Debouncer.Sharp.csproj.
  2. Move the relevant [Unreleased] entries in CHANGELOG.md into a new ## [X.Y.Z] - YYYY-MM-DD section.
  3. Commit both changes.
  4. Tag and push: git tag vX.Y.Z && git push origin vX.Y.Z — this triggers the publish workflow.

Basic debouncer

using Debouncer.Sharp;

using var debouncer = new Debouncer(TimeSpan.FromMilliseconds(250));

await debouncer.HitAsync(async ct =>
{
    await PersistAsync(ct);
});

Each hit cancels any pending action. Only the latest action runs after the delay window.

Async fetch + apply

using Debouncer.Sharp;

var operation = new DebouncedAsync<string, SearchResult>(
    TimeSpan.FromMilliseconds(300),
    fetchAsync: SearchAsync,
    apply: (_, result) => ViewModel.Apply(result),
    shouldApply: (query, _) => query == ViewModel.CurrentQuery,
    cache: null,
    cacheTtl: TimeSpan.FromSeconds(30),
    dispatcher: null);

If you need UI-thread marshalling, pass a GuiDispatcher.Sharp IGuiDispatcher to DebouncedAsync or DebounceFactory — e.g. AvaloniaGuiDispatcher from GuiDispatcher.Sharp.Avalonia, or ImmediateGuiDispatcher for headless/test hosts.

Latest value (coalesce to newest)

using Debouncer.Sharp;

using var settingsSave = factory.CreateLatest<Settings>(
    TimeSpan.FromMilliseconds(750),
    applyAsync: async (settings, ct) => await SaveToDiskAsync(settings, ct));

settingsSave.Hit(currentSettings); // rapid calls coalesce to the last value

Important: Dispose() only cancels the pending timer — it does not flush. If you need a guaranteed write before shutdown, await FlushAsync() explicitly before disposing:

await settingsSave.FlushAsync();
settingsSave.Dispose();

This avoids a sync-over-async deadlock risk when IGuiDispatcher marshals onto a UI thread that a blocking flush-on-dispose could otherwise block against.

Batch (collect then flush, with caps)

using Debouncer.Sharp;

using var telemetry = factory.CreateBatch<TelemetryEvent>(
    TimeSpan.FromSeconds(1),
    flushAsync: async (events, ct) => await SendBatchAsync(events, ct),
    options: new DebouncedBatchOptions
    {
        MaxBatchSize = 1000,       // flush as soon as the buffer reaches this many items
        MaxWait = TimeSpan.FromSeconds(5), // flush even if items keep arriving without a quiet period
    });

telemetry.Add(evt);

A pure debounce would postpone the flush forever under continuous load, since the quiet period never elapses. MaxBatchSize and MaxWait are hard caps that force a flush regardless of ongoing activity — MaxWait should typically be >= delay. Same Dispose()-does-not-flush caveat as IDebouncedLatest<T> applies here.

File system sinks (Debouncer.Sharp.FileSystem)

Ready-made sinks for the most common debounce use case — coalescing writes to a file:

using Debouncer.Sharp.FileSystem;
using System.Text.Json.Serialization;

[JsonSerializable(typeof(Settings))]
internal partial class AppJsonContext : JsonSerializerContext { }

// Atomic JSON snapshot: writes to a temp sibling, then moves it into place.
using var settingsSave = factory.CreateJsonSnapshot(
    "settings.json",
    TimeSpan.FromMilliseconds(750),
    AppJsonContext.Default.Settings,
    onError: ex => log.Warning(ex, "Failed to save settings"));

settingsSave.Hit(currentSettings);

// JSON Lines batch: plain .jsonl append, or additive bursts inside a .jsonl.zip archive.
using var telemetry = factory.CreateJsonLinesBatch(
    "telemetry.jsonl",
    TimeSpan.FromSeconds(1),
    AppJsonContext.Default.TelemetryEvent,
    options: new DebouncedBatchOptions { MaxBatchSize = 1000 },
    zip: true, // omit or set false for a plain, human-readable .jsonl file
    onError: ex => log.Warning(ex, "Failed to write telemetry batch"));

telemetry.Add(evt);

// Reading back a zip-mode batch:
var events = JsonlZipReader.ReadAllRecords("telemetry.jsonl.zip", AppJsonContext.Default.TelemetryEvent);

Both sinks take an optional onError callback invoked instead of throwing when the write fails (a transient disk error shouldn't crash the debouncer); without it, failures propagate through FlushAsync()/the discarded background task, same as the raw tiers. The zip sink also accepts an optional onDiagnostic callback for informational messages (e.g. burst-sequence recovery on restart). There's no logging-framework dependency — these are plain Action delegates, by design.

Deterministic testing

Debounce timers use the standard .NET TimeProvider abstraction. Pass a FakeTimeProvider (Microsoft.Extensions.TimeProvider.Testing) to make quiet-period behaviour deterministic in tests:

using Microsoft.Extensions.Time.Testing;

var time = new FakeTimeProvider();
using var latest = new DebouncedLatest<int>(
    TimeSpan.FromMilliseconds(250),
    applyAsync: async (value, ct) => { /* ... */ },
    timeProvider: time);

latest.Hit(1);
latest.Hit(2);
time.Advance(TimeSpan.FromMilliseconds(250));
await Task.Yield();

// apply ran for value 2 — no real-time sleep required

The same optional timeProvider is available on Debouncer, DebouncedAsync<TIn,TOut>, DebouncedBatch<T>, and DebounceFactory. Omit it in production code.

Dependency injection

using Debouncer.Sharp;
using GuiDispatcher.Sharp;
using Microsoft.Extensions.DependencyInjection;

services.AddDebouncing();

// Optional: register a dispatcher for UI-thread apply
services.AddSingleton<IGuiDispatcher, MyUiDispatcher>();

var factory = serviceProvider.GetRequiredService<IDebounceFactory>();
var cache = serviceProvider.CreateDebounceCache<string, SearchResult>("search");

// The same factory also exposes the Latest and Batch tiers:
var settingsSave = factory.CreateLatest<Settings>(TimeSpan.FromMilliseconds(750), SaveAsync);
var telemetry = factory.CreateBatch<TelemetryEvent>(TimeSpan.FromSeconds(1), SendBatchAsync);

API overview

Type Purpose
Debouncer Simple delayed action debouncing
DebouncedAsync<TIn, TOut> Debounced fetch with optional cache and apply
DebouncedLatest<T> Coalesces to the newest value, applies once after a quiet period
DebouncedBatch<T> Buffers items, flushes as a batch with MaxBatchSize/MaxWait caps
DebouncedBatchOptions MaxBatchSize/MaxWait caps for IDebouncedBatch<T>
DebounceFactory Creates debouncers with optional dispatcher
IDebounceCache<TKey, TValue> Cache abstraction
MemoryCacheDebounceCache<TKey, TValue> IMemoryCache-backed cache
SimpleDictionaryCache<TKey, TValue> In-memory dictionary cache
DebounceServiceExtensions DI registration helpers
DebounceFileSystemExtensions (Debouncer.Sharp.FileSystem) CreateJsonSnapshot/CreateJsonLinesBatch file sinks
JsonlZipReader (Debouncer.Sharp.FileSystem) Reads back a zip-mode JSON Lines batch
Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  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.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on Debouncer.Sharp:

Package Downloads
ComposableSettings

Component-based settings composition with source-generated child wiring, registration, and DI.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.2.0 147 7/10/2026
1.1.6 106 7/8/2026
1.0.6 131 7/1/2026
1.0.5 102 7/1/2026
1.0.3 112 6/29/2026
1.0.2 257 6/1/2026