Documentation

Unity Engine


User Manual

Script Reference

Unity Engine


Create and run a job

Create and schedule a job.
Read time 6 minutesLast updated 18 days ago

To create and successfully run a job, you must do the following:
  • Create a job: Implement the
    IJob
    interface.
  • Schedule the job: Call the
    Schedule
    method on the job.
  • Wait for the job to complete: It returns immediately if the job is already complete, and you can call the
    Complete
    method on the job when you want to access the data.

Create a job

To create a job in Unity, implement the
IJob
interface. You can use your
IJob
implementation to schedule a single job that runs in parallel to any other jobs that are running.
IJob
has one required method:
Execute
, which Unity invokes whenever a worker thread runs the job.
When you create a job, you can also create a
JobHandle
for it, which other methods need to use to reference the job.
Important
There’s no protection against accessing non-readonly or mutable static data from within a job. Accessing this kind of data circumvents all safety systems and might crash your application or the Unity Editor.
When Unity runs, the job system makes a copy of scheduled job data, which prevents more than one thread from reading or writing the same data. Only data written to a
NativeContainer
can be accessed after the job finishes. This is because both the copy of the
NativeContainer
that the job uses and the original
NativeContainer
object point to the same memory. For more information, see the documentation on Thread safe types.
When the job system picks up a job from its job queue, it runs the
Execute
method once on a single thread. Typically, the job system runs jobs on background threads, but it can choose the main thread if it becomes idle. For this reason, you should design your jobs to complete in under a frame.

Schedule a job

To schedule a job, call
Schedule
. This puts the job into the job queue, and the job system begins executing the job once all its dependencies, if any, complete. Once scheduled, you can't interrupt a job. You can only call
Schedule
from the main thread.
Tip
Jobs have a
Run
method that you can use in place of
Schedule
to immediately execute the job on the main thread. You can use this for debugging purposes.

Complete the job

Once you call
Schedule
and the job system has executed a job, you can call the
Complete
method on the
JobHandle
to access the data in the job. It's best practice to call
Complete
as late as possible in your code. When you call
Complete
, the main thread can safely access the
NativeContainer
instance that the job was using. Calling
Complete
also cleans up the state in the safety system. Not doing so introduces a memory leak.

Job examples

The following is an example of a job that adds two floating point values together. It implements
IJob
, uses a
NativeArray
to get the results of the job, and uses the
Execute
method with the implementation of the job inside it:
using UnityEngine;using Unity.Collections;using Unity.Jobs;// Job adding two floating point values togetherpublic struct MyJob : IJob{ public float a; public float b; public NativeArray<float> result; public void Execute() { result[0] = a + b; }}
The following example builds on the
MyJob
job to schedule a job on the main thread:
using UnityEngine;using Unity.Collections;using Unity.Jobs;public class MyScheduledJob : MonoBehaviour{ // Create a native array of a single float to store the result. Using a // NativeArray is the only way you can get the results of the job, whether // you're getting one value or an array of values. NativeArray<float> result; // Create a JobHandle for the job JobHandle handle; // Set up the job public struct MyJob : IJob { public float a; public float b; public NativeArray<float> result; public void Execute() { result[0] = a + b; } } // Update is called once per frame void Update() { // Set up the job data result = new NativeArray<float>(1, Allocator.TempJob); MyJob jobData = new MyJob { a = 10, b = 10, result = result }; // Schedule the job handle = jobData.Schedule(); } private void LateUpdate() { // Sometime later in the frame, wait for the job to complete before accessing the results. handle.Complete(); // All copies of the NativeArray point to the same memory, you can access the result in "your" copy of the NativeArray // float aPlusB = result[0]; // Free the memory allocated by the result array result.Dispose(); }}

Schedule and Complete best practices

It’s best practice to call
Schedule
on a job as soon as you have the data it needs, and don’t call
Complete
on it until you need the results.
You can schedule less important jobs in a part of the frame where they aren’t competing with more important jobs.
For example, if there is a period between the end of one frame and the beginning of the next frame where no jobs are running, and a one frame latency is acceptable, you can schedule the job towards the end of a frame and use its results in the following frame. Alternatively, if your application saturates that changeover period with other jobs, and there's an under-utilized period somewhere else in the frame, it's more efficient to schedule your job there instead.
You can also use the Profiler to see where Unity is waiting for jobs to complete. The marker
WaitForJobGroupID
on the main thread indicates this. This marker might mean that you've introduced a data dependency somewhere that you should resolve. Look for
JobHandle.Complete
to track down where you have data dependencies that are forcing the main thread to wait.

Avoid using long running jobs

Unlike threads, jobs don't yield execution. Once a job starts, that job worker thread commits to completing the job before running any other job. As such, it's best practice to break up long running jobs into smaller jobs that depend on one another, instead of submitting jobs that take a long time to complete relative to other jobs in the system.
The job system usually runs multiple chains of job dependencies, so if you break up long running tasks into multiple pieces there is a chance for multiple job chains to progress. If instead the job system is filled with long running jobs, they might completely consume all worker threads and block independent jobs from executing. This might push out the completion time of important jobs that the main thread explicitly waits for, resulting in stalls on the main thread that otherwise wouldn't exist.
In particular, long running
IJobParallelFor
jobs impact negatively on the job system because these job types intentionally try to run on as many worker threads as possible for the job batch size. If you can't break up long parallel jobs, consider increasing the batch size of your job when scheduling it to limit how many workers pick up the long running job.
MyParallelJob jobData = new MyParallelJob();jobData.Data = someData; jobData.Result = someArray; // Use half the available worker threads, clamped to a minimum of 1 worker threadconst int numBatches = Mathf.Max(1, JobsUtility.JobWorkerCount / 2); const int totalItems = someArray.Length;const int batchSize = totalItems / numBatches;// Schedule the job with one Execute per index in the results array and batchSize items per processing batchJobHandle handle = jobData.Schedule(result.Length, totalItems, batchSize);

Additional resources