{"id":37049833,"url":"https://github.com/khmylov/limited-concurrency","last_synced_at":"2026-01-14T05:47:10.476Z","repository":{"id":48226895,"uuid":"379297597","full_name":"khmylov/limited-concurrency","owner":"khmylov","description":".NET utilities for limited concurrency and parallelism.","archived":false,"fork":false,"pushed_at":"2024-02-12T23:47:12.000Z","size":30,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-11-27T13:04:29.037Z","etag":null,"topics":["concurrency","dotnet","parallelism"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/khmylov.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","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}},"created_at":"2021-06-22T14:25:23.000Z","updated_at":"2023-01-31T18:58:29.000Z","dependencies_parsed_at":"2024-02-13T00:48:41.406Z","dependency_job_id":null,"html_url":"https://github.com/khmylov/limited-concurrency","commit_stats":null,"previous_names":[],"tags_count":8,"template":false,"template_full_name":null,"purl":"pkg:github/khmylov/limited-concurrency","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/khmylov%2Flimited-concurrency","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/khmylov%2Flimited-concurrency/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/khmylov%2Flimited-concurrency/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/khmylov%2Flimited-concurrency/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/khmylov","download_url":"https://codeload.github.com/khmylov/limited-concurrency/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/khmylov%2Flimited-concurrency/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28411402,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-14T05:26:33.345Z","status":"ssl_error","status_checked_at":"2026-01-14T05:21:57.251Z","response_time":107,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":["concurrency","dotnet","parallelism"],"created_at":"2026-01-14T05:47:09.828Z","updated_at":"2026-01-14T05:47:10.470Z","avatar_url":"https://github.com/khmylov.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":".NET utilities for concurrent message processing with configurable degree of parallelism, and per-partition ordering.\n\n```\ndotnet add package LimitedConcurrency\n```\n\n# LimitedParallelExecutor\n\n`LimitedParallelExecutor` allows to run `Task`s with a limited degree of parallelism (i.e. not more than N tasks are run in parallel at any given moment).\nUnlike various similar custom `TaskScheduler` implementations, it maintains the limited degree of parallelism not only for the *synchronous* part of the task execution, but for entire asynchronous operation.\n\n```csharp\nasync Task Job(int delay, string message)\n{\n    Console.WriteLine($\"{message} started\");\n    await Task.Delay(delay).ConfigureAwait(false);\n    Console.WriteLine($\"{message} finished\");\n}\n\nvar executor = new LimitedParallelExecutor(degreeOfParallelism: 2);\nexecutor.Enqueue(() =\u003e Job(2000, \"Job A\"));\nexecutor.Enqueue(() =\u003e Job(1000, \"Job B\"));\nexecutor.Enqueue(() =\u003e Job(500, \"Job C\"));\n```\n\nOutput:\n```\nJob A started\nJob B started\nJob B finished\nJob C started\nJob C finished\nJob A finished\n```\n\n## Notes\n\n- Executor maintains FIFO order, Tasks are started in the order they were enqueued\n    - While the executor itself is thread-safe, multi-threaded concurrent clients may need synchronization to ensure correct enqueuing order.\n- Executor schedules Tasks via `Task.Run`, i.e. on default thread pool scheduler, to ensure that execution is truly parallel even if passed `Func\u003cTask\u003e` implementations are synchronous and blocking.\n\n# ConcurrentPartitioner\n\nAnother common requirement in concurrent message processing is \"partitioning\":\n- Every message belongs to exactly one partition key, for example customer name in multi-tenant environment\n- Messages with different partition keys may be processed in parallel\n- Messages within the same partition key must be processed sequentially (or with a limited max concurrency level)\n\n`ConcurrentPartitioner` provides such behavior.\n\n```csharp\nasync Task\u003cint\u003e Job(int delay, string message)\n{\n    Console.WriteLine($\"{message} started\");\n    await Task.Delay(delay).ConfigureAwait(false);\n    Console.WriteLine($\"{message} finished\");\n    return 0;\n}\n\nvar partitioner = new ConcurrentPartitioner();\n\npartitioner.ExecuteAsync(\"partition A\", () =\u003e Job(100, \"Job A1\"));\npartitioner.ExecuteAsync(\"partition B\", () =\u003e Job(100, \"Job B1\"));\npartitioner.ExecuteAsync(\"partition A\", () =\u003e Job(100, \"Job A2\"));\npartitioner.ExecuteAsync(\"partition B\", () =\u003e Job(100, \"Job B2\"));\n```\n\nExample output:\n```\nJob B1 started\nJob A1 started\nJob B1 finished\nJob A1 finished\nJob A2 started\nJob B2 started\nJob A2 finished\nJob B2 finished\n```\n\n## Notes\n- Unlike `LimitedParallelExecutor`, this partitioner does not guarantee FIFO order **across multiple partitions** (note that B1 may be started before A1)\n    - However, FIFO order is guaranteed within a single partition key\n    - Just like with `LimitedParallelExecutor`, FIFO order is guaranteed when the clients synchronize access to the _synchronous_ part of `ExecuteAsync`\n- You can specify custom per partition concurrency limit via `ConcurrentPartitioner`'s constructor.\n    - This allows to implemented \"keyed limiter\" scenarios, e.g. executing not more than N concurrent jobs per partition at the same time.\n- You can wrap `ConcurrentPartitioner` into another `LimitedParallelExecutor` to enforce a global degree of parallelism across all partitions.\n- `ConcurrentPartitioner` is designed to automatically clean up its internal storage for unused partitions, so you don't have to worry about memory leaks if you generate a huge number of different partition keys over a long period of time.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkhmylov%2Flimited-concurrency","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkhmylov%2Flimited-concurrency","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkhmylov%2Flimited-concurrency/lists"}