WaitForJobCompleted: custom yield instruction that waits for a JobHandle

I just realized that we can write custom yield instructions for coroutines. I’d like to share my implementation of WaitForJobCompleted that takes a JobHandle and ends the coroutine if the handle is completed.

By default, job completion is forced after 4 frames to prevent TempJob allocators from complaining. Pass false as the second parameter if you have a long-running job with a Persistent allocator.

public class WaitForJobCompleted : CustomYieldInstruction
{
    private readonly bool _isUsingTempJobAllocator;
    private readonly int _forceJobCompletionFrameCount;
    private JobHandle _jobHandle;

    public override bool keepWaiting
    {
        get
        {
            if (_isUsingTempJobAllocator && Time.frameCount >= _forceJobCompletionFrameCount)
                _jobHandle.Complete();
           // optional: automatically call Complete() on the handle after it completed
           // side note: yes, you have to complete what has already completed, i still think that's bonkers! :)
           // if (_jobHandle.IsCompleted) _jobHandle.Complete();
            return _jobHandle.IsCompleted == false;
        }
    }

    public WaitForJobCompleted(JobHandle jobHandle, bool isUsingTempJobAllocator = true)
    {
        _jobHandle = jobHandle;
        _isUsingTempJobAllocator = isUsingTempJobAllocator;
 
        // force completion before running into native Allocator.TempJob's lifetime limit of 4 frames
        _forceJobCompletionFrameCount = Time.frameCount + 4;
    }
}

Here’s an example coroutine using WaitForJobCompleted which invokes an Action with the result (in this case: a Mesh instance):

StartCoroutine(WaitForMeshCompleted(mesh => { meshFilter.sharedMesh = mesh; }));
public IEnumerator WaitForMeshCompleted(Action<Mesh> callback)
{
    // check first whether job has already been completed within the current frame!
    // if not completed this frame, just wait till it does complete (at most 4 frames)
    if (IsCompleted() == false)
        yield return new WaitForJobCompleted(_buildJobHandle, isUsingTempJobAllocator: true);

    callback.Invoke(GetMesh());
}
4 Likes

I have tried to use this, but getting error:

InvalidOperationException: The previously scheduled job OrientedBounds:BoundsJob writes to the Unity.Collections.NativeArray1[UnityEngine.Bounds] BoundsJob.result. You must call JobHandle.Complete() on the job OrientedBounds:BoundsJob, before you can read from the Unity.Collections.NativeArray1[UnityEngine.Bounds] safely.

  • when trying to access the job result

But the coroutine fhom here: Unity C# Job System and Coroutines — Lee's Blog runs fine.

The WaitForJobCompleted coroutine isn’t calling Complete() on the JobHandle. Did you call handle.Complete() after the yield instruction and before accessing job data? If not that would explain the issue. Otherwise please post some more code for analysis, thanks!

PS: updated the code to automatically call Complete() when the handle is completed. Couldn’t test it therefore I only added it as a comment which you’ll need to uncomment to try this.

1 Like

Thank you.
That fixes it.

1 Like