FabulousScheduler.Queue
5.0.1
dotnet add package FabulousScheduler.Queue --version 5.0.1
NuGet\Install-Package FabulousScheduler.Queue -Version 5.0.1
<PackageReference Include="FabulousScheduler.Queue" Version="5.0.1" />
<PackageVersion Include="FabulousScheduler.Queue" Version="5.0.1" />
<PackageReference Include="FabulousScheduler.Queue" />
paket add FabulousScheduler.Queue --version 5.0.1
#r "nuget: FabulousScheduler.Queue, 5.0.1"
#:package FabulousScheduler.Queue@5.0.1
#addin nuget:?package=FabulousScheduler.Queue&version=5.0.1
#tool nuget:?package=FabulousScheduler.Queue&version=5.0.1
Queue-based Scheduler
Queue jobs run once per enqueue, in FIFO order. The scheduler pulls the next job from
an IQueue, runs it, reports the result, and moves on. Re-running a job means putting it
back into the queue.
π Contents
Default Job Manager <a id="default" />
QueueJobManager (in FabulousScheduler.Queue) is a static, process-wide facade. Unlike
the recurring manager, it needs an IQueue instance. The required call order is:
1. SetConfig(config, queue) // OPTIONAL config, but queue is required; before RunScheduler
2. RunScheduler() // start the background loop
3. JobResultEvent += // subscribe to results
4. Register(...) // enqueue jobs (only AFTER RunScheduler)
β οΈ Same enforced contracts as the recurring manager:
Register(...)beforeRunScheduler()throwsSchedulerNotRunnableException;SetConfig(...)afterRunScheduler()throwsSetConfigAfterRunSchedulingException.
Register overloads
Register creates a job, enqueues it immediately, and returns its Guid:
Guid Register(Action action);
Guid Register(Action action, string name);
Guid Register(Action action, string name, string category);
Guid Register(Func<Task> action);
Guid Register(Func<Task> action, string name);
Guid Register(Func<Task> action, string name, string category);
Defaults when omitted: name = "anonimouse", category = "internal".
βΉοΈ Jobs registered through
QueueJobManagerare single-run β the manager does not expose theAttemptsbudget. To use the built-inAttemptscounter you build the jobs yourself; see Retry / Attempts.
The result callback
QueueJobManager.JobResultEvent +=
(ref IQueueJob job, ref JobResult<JobOk, JobFail> res) =>
{
if (res.IsSuccess)
Console.WriteLine("{0} ok", job.Name);
else
Console.WriteLine("{0} failed: {1}", job.Name, res.GetFail()!.Message);
};
State machine (QueueJobStateEnum)
Waiting ββdequeuedβββΊ Running ββrun finishedβββΊ Completed
β² β
βββββββββββββ ResetState() βββββββββββββββββββββββ
(then Enqueue again to re-run)
A Completed job will not run again on its own. To re-run it, call ResetState()
(Completed β Waiting) and put it back into the queue.
Fail reasons (QueueJobFailEnum)
| Value | When |
|---|---|
IncorrectState |
ExecuteAsync was called while the job was not Waiting. |
InternalException |
The user delegate threw β the exception is in JobFail.Exception. |
FailedExecute |
The job explicitly returned a JobFail. |
Disposed |
The job was disposed before execution. |
Make my own job manager <a id="myself" />
QueueJobManager wraps two public base classes plus an IQueue. Subclass them when you
need custom job state, your own queue (e.g. a database-backed queue), or DI instead of a
static singleton.
1. A custom job β derive from BaseQueueJob and implement ActionJob(). The
constructor also takes the optional Attempts budget:
using FabulousScheduler.Queue.Abstraction;
using FabulousScheduler.Queue.Result;
using FabulousScheduler.Core.Types;
public sealed class MyQueueJob : BaseQueueJob
{
public MyQueueJob(string name, string category, byte? attempts)
: base(name, category, isAsyncAction: true, attempts) { }
protected override async Task<JobResult<JobOk, JobFail>> ActionJob()
{
await Task.Delay(10);
return new JobOk(ID, Name); // success
// return new JobFail(QueueJobFailEnum.FailedExecute, ID, "reason"); // failure
}
}
2. A custom scheduler β derive from BaseQueueScheduler. The base loop pulls jobs
from the protected IQueue Queue, so registration is just an enqueue:
using FabulousScheduler.Queue.Abstraction;
using FabulousScheduler.Queue.Interfaces;
public sealed class MyQueueScheduler : BaseQueueScheduler
{
public MyQueueScheduler(Configuration? config, IQueue queue) : base(config, queue) { }
public Guid Add(MyQueueJob job)
{
base.Queue.Enqueue(job);
return job.ID;
}
}
3. A custom queue β implement IQueue to back the queue with anything you like
(Redis, PostgreSQL, β¦). The contract is small:
public interface IQueue
{
int Count { get; }
void Enqueue(IQueueJob job);
Task<IQueueJob> NextAsync(); // returns the next job, or completes later when one arrives
}
NextAsync() is expected to wait (asynchronously) when the queue is empty and complete
once a job is enqueued.
Usage <a id="usage" />
BaseQueueJob <a id="basequeuejob" />
public abstract class BaseQueueJob : IQueueJob. Thread-safe state, optional Attempts
budget, work delegated to the abstract ActionJob().
| Member | Description |
|---|---|
ID, Name, Category |
Identity (see Core.md). |
Attempts |
byte? β remaining attempts. Decremented on each run when set; null means "not tracked". |
State |
Current QueueJobStateEnum. |
TotalRun |
uint β number of times the job started. |
LastExecute / LastSuccessExecute |
Timestamps (nullable). |
Task<JobResult<JobOk,JobFail>> ExecuteAsync() |
Runs the job once. |
void ResetState() |
Completed β Waiting, so the job can be re-enqueued. |
protected abstract Task<JobResult<JobOk,JobFail>> ActionJob() |
Your work. |
Dispose() / DisposeAsync() |
Marks the job disposed. |
Constructor:
protected BaseQueueJob(string name, string category, bool isAsyncAction, byte? attempts);
BaseQueueScheduler <a id="basequeueshceduler" />
public class BaseQueueScheduler : IQueueJobScheduler (constructor is protected, so use
it via a subclass). Runs one LongRunning background loop that awaits Queue.NextAsync().
| Member | Description |
|---|---|
event JobResultEventHandler JobResultEvent |
Fired after each job run with (ref IQueueJob, ref JobResult<JobOk,JobFail>). |
void RunScheduler() |
Starts the loop. Idempotent. |
protected readonly IQueue Queue |
The backing queue β Enqueue to add work. |
void Dispose() |
Requests cancellation and releases resources. |
A SemaphoreSlim caps concurrent runs at Configuration.MaxParallelJobExecute.
InMemoryQueue <a id="inmemoryqueue" />
FabulousScheduler.Queue.Queues.InMemoryQueue is the built-in IQueue. It keeps a FIFO
of pending jobs and a FIFO of waiting NextAsync() requests; enqueuing a job either lands
in the backlog or directly completes a waiting request.
| Member | Description |
|---|---|
InMemoryQueue(int? capacity = null) |
Optional capacity hint for the waiting-requests queue. |
int Count |
Number of backlogged jobs (not counting in-flight ones). |
void Enqueue(IQueueJob job) |
Add a single job. |
void Enqueue(IEnumerable<IQueueJob> jobs) |
Add many jobs (extra method, not on IQueue). |
Task<IQueueJob> NextAsync() |
Next job, or a task that completes when one is enqueued. |
Retry / Attempts <a id="attempts" />
BaseQueueScheduler does not retry automatically. Retry is the caller's
responsibility: when a job fails and still has budget, reset it and enqueue it again.
The Attempts budget on a job is a byte? that the base decrements on each run. Because
QueueJobManager.Register does not expose it, the budget is only available when you build
the jobs yourself (custom job + custom scheduler, or by holding your own IQueue):
var queue = new InMemoryQueue();
var scheduler = new MyQueueScheduler(Configuration.Default, queue);
scheduler.JobResultEvent += (ref IQueueJob sender, ref JobResult<JobOk, JobFail> res) =>
{
if (res.IsFail && sender.Attempts is > 0)
{
sender.ResetState(); // Completed -> Waiting
queue.Enqueue(sender); // try again
}
};
scheduler.RunScheduler();
scheduler.Add(new MyQueueJob("retryable", "demo", attempts: 3));
If you are using the default QueueJobManager, keep a reference to the queue you passed
to SetConfig and re-enqueue from the callback using your own retry counter (the built-in
Attempts will be null).
Configuration
FabulousScheduler.Queue.Configuration:
| Property | Default | Meaning |
|---|---|---|
MaxParallelJobExecute |
Environment.ProcessorCount * 2 |
Max jobs running at once. |
var config = new Configuration(maxParallelJobExecute: 5);
// or: Configuration.Default;
Example <a id="example" />
using FabulousScheduler.Queue;
using FabulousScheduler.Queue.Interfaces;
using FabulousScheduler.Queue.Queues;
using FabulousScheduler.Queue.Result;
using FabulousScheduler.Core.Types;
var queue = new InMemoryQueue(1);
var config = new Configuration(maxParallelJobExecute: 5);
QueueJobManager.SetConfig(config, queue);
// Start the scheduler BEFORE registering jobs
QueueJobManager.RunScheduler();
// Subscribe to results
QueueJobManager.JobResultEvent += (ref IQueueJob job, ref JobResult<JobOk, JobFail> res) =>
{
var now = DateTime.Now;
if (res.IsSuccess)
Console.WriteLine("[{0:hh:mm:ss}] {1} {2} OK", now, job.Name, res.JobID);
else
Console.WriteLine("[{0:hh:mm:ss}] {1} {2} FAIL", now, job.Name, res.JobID);
};
// Enqueue five one-shot jobs
for (var i = 0; i < 5; i++)
{
QueueJobManager.Register(
action: () =>
{
int a = 10, b = 100;
int c = a + b;
_ = c;
},
name: $"ExampleJob_{i}"
);
}
Thread.Sleep(-1); // keep the process alive
Benchmarks <a id="benchmarks" />
Planned.
Performance <a id="performance" />
Planned.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. 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. |
-
net8.0
- FabulousScheduler.Core (>= 5.0.1)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on FabulousScheduler.Queue:
| Package | Downloads |
|---|---|
|
FabulousScheduler
High-performance scheduler for recurring and queue-based jobs. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 5.0.1 | 122 | 6/18/2026 |
| 5.0.0 | 123 | 6/18/2026 |
| 4.0.0 | 298 | 3/20/2025 |
| 3.1.3 | 10,039 | 6/14/2024 |
| 3.1.0 | 11,973 | 2/3/2024 |
| 3.0.1 | 268 | 1/27/2024 |
| 2.2.7 | 603 | 1/17/2024 |
| 2.2.6 | 238 | 1/17/2024 |
| 2.2.5 | 237 | 1/17/2024 |
| 2.2.4 | 228 | 1/17/2024 |
| 2.2.3 | 241 | 1/12/2024 |
| 2.2.2 | 261 | 1/9/2024 |
| 2.2.1 | 258 | 1/8/2024 |
| 2.1.6 | 285 | 1/2/2024 |
| 2.1.5 | 293 | 1/1/2024 |
| 2.1.2 | 267 | 1/1/2024 |
| 2.1.1 | 271 | 1/1/2024 |