{"id":15650020,"url":"https://github.com/aldaviva/throttledebounce","last_synced_at":"2025-04-30T16:41:12.416Z","repository":{"id":41092657,"uuid":"150860083","full_name":"Aldaviva/ThrottleDebounce","owner":"Aldaviva","description":"🚗 Rate-limit your actions and funcs by throttling and debouncing them. Retry when an exception is thrown.","archived":false,"fork":false,"pushed_at":"2025-04-12T11:27:55.000Z","size":248,"stargazers_count":47,"open_issues_count":0,"forks_count":6,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-04-26T10:37:46.963Z","etag":null,"topics":["debounce","rate-limiting","retry","throttle"],"latest_commit_sha":null,"homepage":"https://www.nuget.org/packages/ThrottleDebounce/","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Aldaviva.png","metadata":{"files":{"readme":"Readme.md","changelog":null,"contributing":null,"funding":null,"license":"License.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"custom":["https://paypal.me/aldaviva"]}},"created_at":"2018-09-29T12:01:52.000Z","updated_at":"2025-04-25T08:51:11.000Z","dependencies_parsed_at":"2024-06-21T17:39:09.026Z","dependency_job_id":"36ad1cfe-27a1-4b88-b5e3-253e180cb260","html_url":"https://github.com/Aldaviva/ThrottleDebounce","commit_stats":null,"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Aldaviva%2FThrottleDebounce","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Aldaviva%2FThrottleDebounce/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Aldaviva%2FThrottleDebounce/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Aldaviva%2FThrottleDebounce/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Aldaviva","download_url":"https://codeload.github.com/Aldaviva/ThrottleDebounce/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251745455,"owners_count":21637043,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["debounce","rate-limiting","retry","throttle"],"created_at":"2024-10-03T12:33:02.334Z","updated_at":"2025-04-30T16:41:12.404Z","avatar_url":"https://github.com/Aldaviva.png","language":"C#","funding_links":["https://paypal.me/aldaviva"],"categories":[],"sub_categories":[],"readme":"ThrottleDebounce\n===\n\n[![Package Version](https://img.shields.io/nuget/v/ThrottleDebounce?logo=nuget\u0026label=version)](https://www.nuget.org/packages/ThrottleDebounce/) [![NuGet Gallery Download Count](https://img.shields.io/nuget/dt/ThrottleDebounce?logo=nuget\u0026color=blue\n)](https://www.nuget.org/packages/ThrottleDebounce/) [![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/Aldaviva/ThrottleDebounce/dotnetpackage.yml?branch=master\u0026logo=github)](https://github.com/Aldaviva/ThrottleDebounce/actions/workflows/dotnetpackage.yml) [![Testspace](https://img.shields.io/testspace/tests/Aldaviva/Aldaviva:ThrottleDebounce/master?passed_label=passing\u0026failed_label=failing\u0026logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA4NTkgODYxIj48cGF0aCBkPSJtNTk4IDUxMy05NCA5NCAyOCAyNyA5NC05NC0yOC0yN3pNMzA2IDIyNmwtOTQgOTQgMjggMjggOTQtOTQtMjgtMjh6bS00NiAyODctMjcgMjcgOTQgOTQgMjctMjctOTQtOTR6bTI5My0yODctMjcgMjggOTQgOTQgMjctMjgtOTQtOTR6TTQzMiA4NjFjNDEuMzMgMCA3Ni44My0xNC42NyAxMDYuNS00NFM1ODMgNzUyIDU4MyA3MTBjMC00MS4zMy0xNC44My03Ni44My00NC41LTEwNi41UzQ3My4zMyA1NTkgNDMyIDU1OWMtNDIgMC03Ny42NyAxNC44My0xMDcgNDQuNXMtNDQgNjUuMTctNDQgMTA2LjVjMCA0MiAxNC42NyA3Ny42NyA0NCAxMDdzNjUgNDQgMTA3IDQ0em0wLTU1OWM0MS4zMyAwIDc2LjgzLTE0LjgzIDEwNi41LTQ0LjVTNTgzIDE5Mi4zMyA1ODMgMTUxYzAtNDItMTQuODMtNzcuNjctNDQuNS0xMDdTNDczLjMzIDAgNDMyIDBjLTQyIDAtNzcuNjcgMTQuNjctMTA3IDQ0cy00NCA2NS00NCAxMDdjMCA0MS4zMyAxNC42NyA3Ni44MyA0NCAxMDYuNVMzOTAgMzAyIDQzMiAzMDJ6bTI3NiAyODJjNDIgMCA3Ny42Ny0xNC44MyAxMDctNDQuNXM0NC02NS4xNyA0NC0xMDYuNWMwLTQyLTE0LjY3LTc3LjY3LTQ0LTEwN3MtNjUtNDQtMTA3LTQ0Yy00MS4zMyAwLTc2LjY3IDE0LjY3LTEwNiA0NHMtNDQgNjUtNDQgMTA3YzAgNDEuMzMgMTQuNjcgNzYuODMgNDQgMTA2LjVTNjY2LjY3IDU4NCA3MDggNTg0em0tNTU3IDBjNDIgMCA3Ny42Ny0xNC44MyAxMDctNDQuNXM0NC02NS4xNyA0NC0xMDYuNWMwLTQyLTE0LjY3LTc3LjY3LTQ0LTEwN3MtNjUtNDQtMTA3LTQ0Yy00MS4zMyAwLTc2LjgzIDE0LjY3LTEwNi41IDQ0UzAgMzkxIDAgNDMzYzAgNDEuMzMgMTQuODMgNzYuODMgNDQuNSAxMDYuNVMxMDkuNjcgNTg0IDE1MSA1ODR6IiBmaWxsPSIjZmZmIi8%2BPC9zdmc%2B)](https://aldaviva.testspace.com/spaces/298071) [![Coveralls](https://img.shields.io/coveralls/github/Aldaviva/ThrottleDebounce?logo=coveralls)](https://coveralls.io/github/Aldaviva/ThrottleDebounce?branch=master)\n\n*Rate-limit your actions and funcs by throttling and debouncing them. Retry when an exception is thrown.*\n\nThis is a .NET library that lets you rate-limit delegates so they are only executed at most once in a given interval, even if they are invoked multiple times in that interval. You can also invoke a delegate and automatically retry it if it fails.\n\n\u003c!-- MarkdownTOC autolink=\"true\" bracket=\"round\" autoanchor=\"false\" levels=\"1,2,3\" --\u003e\n\n- [Installation](#installation)\n- [Rate limiting](#rate-limiting)\n    - [Usage](#usage)\n    - [Understanding throttling and debouncing](#understanding-throttling-and-debouncing)\n    - [Examples](#examples)\n- [Retrying](#retrying)\n    - [Usage](#usage-1)\n    - [Example](#example)\n\n\u003c!-- /MarkdownTOC --\u003e\n\n## Installation\nThis package is [available on NuGet Gallery](https://www.nuget.org/packages/ThrottleDebounce/).\n```powershell\ndotnet add package ThrottleDebounce\n```\n```powershell\nInstall-Package ThrottleDebounce\n```\n\nIt targets [.NET Standard 2.0](https://learn.microsoft.com/en-us/dotnet/standard/net-standard?tabs=net-standard-2-0) and .NET Framework 4.5.2, so it should be compatible with many runtimes.\n\n## Rate limiting\n\n### Usage\n\n```cs\nAction originalAction;\nFunc\u003cint\u003e originalFunc;\n\nTimeSpan wait = TimeSpan.FromMilliseconds(50);\nusing RateLimitedAction throttledAction = Throttler.Throttle(originalAction, wait, leading: true, trailing: true);\nusing RateLimitedFunc\u003cint\u003e debouncedFunc = Debouncer.Debounce(originalFunc, wait, leading: false, trailing: true);\n\nthrottledAction.Invoke();\nint? result = debouncedFunc.Invoke();\n```\n\n1. Call **`Throttler.Throttle`** to throttle your delegate, or **`Debouncer.Debounce`** to debounce it. Pass\n    1. **`Action action`/`Func func`** — your delegate to rate-limit\n    1. **`TimeSpan wait`** — how long to wait between executions\n    1. **`bool leading`** — `true` if the first invocation should be executed immediately, or `false` if it should be queued. Optional, defaults to `true` for throttling and `false` for debouncing.\n    1. **`bool trailing`** — `true` if subsequent invocations in the waiting period should be enqueued for later execution once the waiting interval is over, or `false` if they should be discarded. Optional, defaults to `true`.\n1. Call the resulting `RateLimitedAction`/`RateLimitedFunc` object's **`Invoke`** method to enqueue an invocation.\n    - `RateLimitedFunc.Invoke` will return `default` (e.g. `null`) if `leading` is `false` and the rate-limited `Func` has not been executed before. Otherwise, it will return the `Func`'s most recent return value.\n1. Your delegate will be executed at the desired rate.\n1. Optionally call the `RateLimitedAction`/`RateLimitedFunc` object's `Dispose()` method to prevent all queued executions from running when you are done.\n\n### Understanding throttling and debouncing\n\n#### Summary\nThrottling and debouncing both restrict a function to not **execute** too often, no matter how frequently you **invoke** it.\n\nThis is useful if the function is invoked very frequently, like whenever the mouse moves, but you don't want to it to run every single time the pointer moves 1 pixel, because the function is expensive, such as rendering a user interface.\n\n**Throttling** allows the function to still be executed periodically, even with a constant stream of invocations.\n\n**Debouncing** prevents the function from being executed at all until it hasn't been invoked for a while.\n\nAn invocation can result in at most one execution. For example, if both `leading` and `trailing` are `true`, one single invocation will execute once on the leading edge and not on the trailing edge.\n\nNot all extra invocations are queued to run on the trailing edge \u0026mdash; only the latest extra invocation is saved, and the other extras are dropped. For example, if you throttle mouse movement and then quickly move your pointer across your screen, only a few of the move event callbacks will be executed, many pixels apart; it won't slowly execute thousands of callbacks all spread out over a long time.\n\n#### Diagram\n\n[![Strategies for Rate-Limiting](https://i.imgur.com/ynlwKtm.png)](https://aldaviva.com/portfolio.html#ratelimiting)\n\n#### Lodash documentation\n\n- [`_.throttle()`](https://lodash.com/docs/#throttle)\n- [`_.debounce()`](https://lodash.com/docs/#debounce)\n\n#### Article and demo\n[*Debouncing and Throttling Explained Through Examples* by David Corbacho](https://css-tricks.com/debouncing-throttling-explained-examples/)\n\n### Examples\n\n#### Throttle an action to execute at most every 1 second\n```cs\nAction throttled = Throttler.Throttle(() =\u003e Console.WriteLine(\"hi\"), TimeSpan.FromSeconds(1)).Invoke;\n\nthrottled(); //logs at 0s\nthrottled(); //logs at 1s\nThread.Sleep(1000);\nthrottled(); //logs at 2s\n```\n\n#### Debounce a function to execute after no invocations for 200 milliseconds\n```cs\nFunc\u003cdouble, double, double\u003e debounced = Debouncer.Debounce((double x, double y) =\u003e Math.Sqrt(x * x + y * y),\n    TimeSpan.FromMilliseconds(200)).Invoke;\n\ndouble? result;\nresult = debounced(1, 1); //never runs\nresult = debounced(2, 2); //never runs\nresult = debounced(3, 4); //runs at 200ms\n```\n\n#### Canceling a rate-limited action so any queued executions won't run\n```cs\nRateLimitedAction rateLimited = Throttler.Throttle(() =\u003e Console.WriteLine(\"hello\"), TimeSpan.FromSeconds(1));\n\nrateLimited.Invoke(); //runs at 0s\nrateLimited.Dispose();\nrateLimited.Invoke(); //never runs\n```\n\n#### Save a WPF window's position to the registry at most every 1 second\n```cs\nstatic void SaveWindowLocation(double x, double y) =\u003e Registry.SetValue(@\"HKEY_CURRENT_USER\\Software\\My Program\", \n    \"Window Location\", $\"{x},{y}\");\n\nAction\u003cdouble, double\u003e saveWindowLocationThrottled = Throttler.Throttle\u003cdouble, double\u003e(saveWindowLocation, \n    TimeSpan.FromSeconds(1)).Invoke;\n\nLocationChanged += (sender, args) =\u003e SaveWindowLocationThrottled(Left, Top);\n```\n\n#### Prevent accidental double-clicks on a WPF button\n```cs\npublic MainWindow(){\n    InitializeComponent();\n\n    Action\u003cobject, RoutedEventArgs\u003e onButtonClickDebounced = Debouncer.Debounce\u003cobject, RoutedEventArgs\u003e(\n        OnButtonClick, TimeSpan.FromMilliseconds(40), true, false).Invoke;\n\n    MyButton.Click += new RoutedEventHandler(onButtonClickDebounced);\n}\n\nprivate void OnButtonClick(object sender, RoutedEventArgs e) {\n    MessageBox.Show(\"Button clicked\");\n}\n```\n\n## Retrying\n\nGiven a function or action, you can execute it and, if it threw an exception, automatically execute it again until it succeeds.\n\n### Usage\n```cs\nRetrier.Attempt(attempt =\u003e MyErrorProneAction(), maxAttempts: 2);\n```\n\n1. Call **`Retrier.Attempt`**. Pass\n    1. **`Action\u003cint\u003e action`/`Func\u003cint, T\u003e func`** — your delegate to attempt, and possibly retry if it throws exceptions. The attempt number will be passed as the `int` parameter, starting with `0` before the first attempt, and `1` before the first retry. If this func returns a `Task`, it will be awaited to determine if it threw an exception.\n    1. **`int? maxAttempts`** — the total number of times the delegate is allowed to run in this invocation, equal to `1` initial attempt plus up to `maxAttempts - 1` retries if it throws an exception. Must be at least 1, if you pass 0 it will clip to 1. Defaults to 2. For infinite retries, pass `null`.\n    1. **`Func\u003cint, TimeSpan\u003e? delay`** — how long to wait between attempts, as a function of the number of retries that have already run, starting with `0` after the first attempt and before the first retry. You can return a constant `TimeSpan` for a fixed delay, or pass longer values for subsequent attempts to implement, for example, exponential backoff. Optional, defaults to `null`, which means no delay. The minimum value is `0`, the maximum value is `int.MaxValue` (`uint.MaxValue - 1` starting in .NET 6), and values outside this range will be clipped.\n    1. **`Func\u003cException, bool\u003e? isRetryAllowed`** — whether the delegate is permitted to execute again after a given `Exception` instance. Return `true` to allow or `false` to deny retries. For example, you may want to retry after HTTP 500 errors since subsequent requests may succeed, but stop after the first failure for an HTTP 403 error which probably won't succeed if the same request is sent again. Optional, `null` defaults to retrying on all exceptions besides `OutOfMemoryException`.\n    1. **`Action beforeRetry`/`Func\u003cTask\u003e beforeRetry`** — a delegate to run extra logic between attempts, for example, if you want to log a message or perform any cleanup before the next attempt. Optional, defaults to not running anything between attempts.\n    1. **`CancellationToken cancellationToken`** — used to cancel the attempts and delays before they have all completed. Optional, defaults to no cancellation token. When cancelled, `Attempt` throws a `TaskCancelledException`.\n1. If your delegate returns a value, it will be returned by `Attempt`.\n\n### Example\n\n#### Send at most 5 HTTP requests, 2 seconds apart, until a 200 response is received\n```cs\nusing HttpClient httpClient = new();\nHttpStatusCode statusCode = await Retrier.Attempt(async attempt =\u003e {\n    Console.WriteLine($\"Attempt #{attempt:N0}...\");\n    using HttpResponseMessage response = await httpClient.GetAsync(\"https://httpbin.org/status/200%2C500\");\n\n    Console.WriteLine($\"Received response status code {(int) response.StatusCode}.\");\n    response.EnsureSuccessStatusCode(); // throws HttpRequestException for status codes outside the range [200, 300)\n    return response.StatusCode;\n}, 5, _ =\u003e TimeSpan.FromSeconds(2));\nConsole.WriteLine($\"Final response: {(int) statusCode}\");\n```\n```text\nAttempt #0...\nReceived response status code 500\nAttempt #1...\nReceived response status code 500\nAttempt #2...\nReceived response status code 200\nFinal response: 200\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faldaviva%2Fthrottledebounce","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Faldaviva%2Fthrottledebounce","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faldaviva%2Fthrottledebounce/lists"}