Quick answer: Call jobHandle.Complete() before reading the output NativeArray on the main thread. Or chain into another job via dependencies to stay parallel.
A flocking simulation runs an IJobParallelFor to compute boid velocities, then assigns them back to Transforms. Unity logs “Job has not finished writing to the … NativeArray”. The main thread is reading before the job finishes.
Complete on Read
var handle = job.Schedule(count, 64);
// ... other work ...
handle.Complete(); // blocks until done
for (int i = 0; i < count; i++)
transforms[i].position = positions[i];
Simple but blocks main thread. Useful when the data is needed this frame.
Chain Jobs Instead
var handle1 = boidJob.Schedule(count, 64);
var applyJob = new ApplyPositionsJob { positions = positions, transforms = transformAccessArray };
var handle2 = applyJob.Schedule(transformAccessArray, handle1);
handle2.Complete(); // at end of frame
Chain via dependency. Schedule next job with previous handle. Apply runs after boidJob, both on worker threads.
Diagnose with Safety System
Editor enables safety checks. If you ever see “has not finished writing”, the data race exists. Disabling safety for performance is fine in release, but fix the race first.
Last-Resort Complete in LateUpdate
void Update() {
handle = job.Schedule(count, 64);
}
void LateUpdate() {
handle.Complete();
// read here
}
Gives the job a full Update cycle to run on workers. Most jobs finish before LateUpdate; only blocks if long-running.
Avoid Forgetting Complete
Outstanding JobHandles at frame end cause WorkerThread leak warnings. Make completion a discipline:
- Always pair Schedule with Complete (or chain to a final handle that gets completed).
- Use JobHandle.ScheduleBatchedJobs() to hint scheduler to start work early.
Verifying
Run Play. No “has not finished writing” errors. Profiler shows worker threads active. Frame time within target with parallelism utilized.
“Schedule, chain, complete. Three primitives, every safe Job-System data flow.”
For large boid counts (10k+), chain everything and complete in LateUpdate — lets workers overlap render setup.