https://github.com/chunty/taskturnstile
A thread-safe named task lifecycle manager for .NET. Prevents duplicate background job execution across threads and across multiple app instances via Redis or SQL Server.
https://github.com/chunty/taskturnstile
background-jobs background-tasks concurrency csharp dotnet nuget task-locking task-manager
Last synced: 25 days ago
JSON representation
A thread-safe named task lifecycle manager for .NET. Prevents duplicate background job execution across threads and across multiple app instances via Redis or SQL Server.
- Host: GitHub
- URL: https://github.com/chunty/taskturnstile
- Owner: chunty
- License: mit
- Created: 2026-05-01T00:10:28.000Z (2 months ago)
- Default Branch: master
- Last Pushed: 2026-05-14T10:51:38.000Z (about 2 months ago)
- Last Synced: 2026-05-14T12:38:57.157Z (about 2 months ago)
- Topics: background-jobs, background-tasks, concurrency, csharp, dotnet, nuget, task-locking, task-manager
- Language: C#
- Homepage:
- Size: 77.1 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# Task Turnstile
[](https://www.nuget.org/packages/TaskTurnstile)
[](https://www.nuget.org/packages/TaskTurnstile)
[](https://github.com/chunty/TaskTurnstile/actions/workflows/ci.yml)
[](https://github.com/chunty/TaskTurnstile/actions/workflows/publish-wiki.yml)
Prevents duplicate background job execution in .NET — within a single instance **and across multiple instances** via a distributed backing store. Perfect for scheduled jobs running on multiple pods, servers, or worker processes where only one should run at a time.
> **Think of it like a turnstile.** Every job that wants to run must push through first. Only one can hold the bar at a time — others wait their turn or are sent away. When the job is done, the bar rotates and the next one can step through.
## Why?
Scheduled jobs (Coravel, Hangfire, Quartz) fire on a timer. If the previous run hasn't finished, you don't want a second one to start. `TaskTurnstile` gives you a named gate:
```csharp
if (!await manager.CanStartAsync("import-job"))
return; // already running, skip this tick
```
Unlike a simple lock, the state can survive app restarts (via Redis or SQL Server) and be shared across multiple instances of your app.
## Install
```
dotnet add package TaskTurnstile
```
## Quick start
```csharp
builder.Services.AddTaskTurnstile();
```
```csharp
bool ran = await manager.TryRunAsync("import-job", async ct =>
{
await DoImportAsync(ct);
});
```
Returns `true` after the work completes, or `false` immediately if the task is already running.
## Scale out across instances
The default store is in-memory — great for a single instance. Add a backing store to prevent duplicate runs across **multiple pods, servers, or worker processes**:
**Redis**
```csharp
builder.Services.AddTaskTurnstile()
.AddRedisStore(o => o.Configuration = "localhost:6379");
```
**SQL Server**
```csharp
builder.Services.AddTaskTurnstile()
.AddSqlServerStore(o => o.ConnectionString = "Server=.;Database=MyApp;...");
```
**Shared `IDistributedCache`** (use your app's existing distributed cache)
```csharp
builder.Services.AddTaskTurnstile()
.AddDistributedStore();
```
> All three options use an in-process memory short-circuit — the backing store is only hit when a task isn't already known to be running locally, keeping overhead minimal. See [Setup](https://github.com/chunty/TaskTurnstile/wiki/Setup) for full options.
## Documentation
| | |
|---|---|
| [Setup](https://github.com/chunty/TaskTurnstile/wiki/Setup) | In-memory, Redis, SQL Server, shared `IDistributedCache`, and all options |
| [API Reference](https://github.com/chunty/TaskTurnstile/wiki/API-Reference) | Full `ITaskStateManager` method reference |
| [Patterns](https://github.com/chunty/TaskTurnstile/wiki/Patterns) | Coravel, BackgroundService, manual start/stop, returning values |
| [Testing](https://github.com/chunty/TaskTurnstile/wiki/Testing) | `FakeTaskStateManager` and manual Moq / NSubstitute setup |
| [Custom Store](https://github.com/chunty/TaskTurnstile/wiki/Custom-Store) | Implement `ITaskStateStore` to use any backing store |
## Packages
| Package | NuGet | Purpose |
|---|---|---|
| `TaskTurnstile` | [](https://www.nuget.org/packages/TaskTurnstile) | Core package |
| `TaskTurnstile.Redis` | [](https://www.nuget.org/packages/TaskTurnstile.Redis) | Dedicated Redis backing store |
| `TaskTurnstile.SqlServer` | [](https://www.nuget.org/packages/TaskTurnstile.SqlServer) | SQL Server backing store |
| `TaskTurnstile.Testing` | [](https://www.nuget.org/packages/TaskTurnstile.Testing) | `FakeTaskStateManager` test double |