A high-performance alloc-free C# job scheduler.
Schedules and executes jobs on a set of worker threads with automatic pooling of internal handles.
public class HeavyCalculation : IJob
{
public void Execute()
{
Thread.Sleep(50); // Simulate heavy work
Console.WriteLine("Done");
}
}
// Create a new Scheduler, which you should keep the lifetime of your program. This is the only API call that will allocate or generate garbage.
var scheduler = new JobScheduler(new JobScheduler.Config()
{
// Names the process "MyProgram0", "MyProgram1", etc.
ThreadPrefixName = "MyProgram",
// Automatically chooses threads based on your processor count
ThreadCount = 0,
// The amount of jobs that can exist in the queue at once without the scheduler spontaneously allocating and generating garbage.
// Past this number, the scheduler is no longer Zero-Alloc!
// Higher numbers slightly decrease performance and increase memory consumption, so keep this on the lowest possible end for your application.
MaxExpectedConcurrentJobs = 64,
// Enables or disables strict allocation mode: if more jobs are scheduled at once than MaxExpectedConcurrentJobs, it throws an error.
// Not recommended for production code, but good for debugging allocation issues.
StrictAllocationMode = false,
});
// You need to pool/create jobs by yourself. This will, of course, allocate, so cache and reuse the jobs.
var firstJob = new HeavyCalculation();
var firstHandle = scheduler.Schedule(firstJob); // Schedules job locally
scheduler.Flush(); // Dispatches all scheduled jobs to the worker threads
firstHandle.Complete(); // Blocks the thread until the job is complete.
// Call Dispose at program exit, which shuts down all worker threads
scheduler.Dispose();
To set a sequential dependency on a job, simply pass a created JobHandle
to JobScheduler.Schedule(job, dependency)
.
var handle1 = scheduler.Schedule(job1);
var handle2 = scheduler.Schedule(job2, handle1); // job2 will only begin execution once job1 is complete!
scheduler.Flush();
Use Scheduler.CombineDependencies(JobHandle[] handles)
to get a new handle that depends on the handles in parallel. That handle can then be passed into future Schedule
call as a dependency itself!
// You must create the array of handles, and handle caching/storage yourself.
JobHandle[] handles = new JobHandle[2];
handles[0] = Scheduler.Schedule(job1);
handles[1] = Scheduler.Schedule(job2);
JobHandle combinedHandle = Scheduler.CombineDependencies(handles); // Combines all handles into the array into one
var dependantHandle = Scheduler.Schedule(job3, combinedHandle); // job3 now depends on job1 and job2.
// job1 and job2 can Complete() in parallel, but job3 can only run once both are complete.
dependantHandle.Complete(); // Blocks the main thread until all three tasks are complete.
Rather than using CombineDependencies()
, if you just need to block the main thread until a list of handles are complete, you can use this syntax:
JobHandle.CompleteAll(JobHandle[] handles); // Waits for all JobHandles to finish, and blocks the main thread until they each complete (in any order)
JobHandle.CompleteAll(List<JobHandle> handles);
Or, if you don't want to maintain a list or array, you can just call handle.Complete()
on all your handles, in any order.