FabulousScheduler.Queue 5.0.1

dotnet add package FabulousScheduler.Queue --version 5.0.1
                    
NuGet\Install-Package FabulousScheduler.Queue -Version 5.0.1
                    
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="FabulousScheduler.Queue" Version="5.0.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="FabulousScheduler.Queue" Version="5.0.1" />
                    
Directory.Packages.props
<PackageReference Include="FabulousScheduler.Queue" />
                    
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 FabulousScheduler.Queue --version 5.0.1
                    
#r "nuget: FabulousScheduler.Queue, 5.0.1"
                    
#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 FabulousScheduler.Queue@5.0.1
                    
#: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=FabulousScheduler.Queue&version=5.0.1
                    
Install as a Cake Addin
#tool nuget:?package=FabulousScheduler.Queue&version=5.0.1
                    
Install as a Cake Tool

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(...) before RunScheduler() throws SchedulerNotRunnableException;
  • SetConfig(...) after RunScheduler() throws SetConfigAfterRunSchedulingException.

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 QueueJobManager are single-run β€” the manager does not expose the Attempts budget. To use the built-in Attempts counter 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 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. 
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 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