Search Unity

From Docs: It is not recommended to Complete a job immediately,

Discussion in 'Entity Component System' started by vincismurf, Jan 31, 2018.

  1. vincismurf

    vincismurf

    Joined:
    Feb 28, 2013
    Posts:
    200
    In the API docs ( https://docs.unity3d.com/2018.1/Documentation/ScriptReference/Unity.Jobs.IJob.html) for Jobs it states

    // Schedule the job, returns the JobHandle which can be waited upon later on
    JobHandle jobHandle = job.Schedule();

    // Ensure the job has completed
    // It is not recommended to Complete a job immediately,
    // since that gives you no actual parallelism.
    // You optimally want to schedule a job early in a frame and then wait for it later in the frame.
    jobHandle.Complete();



    My question is HOW?

    Should you use Script Execution Order to Execute that a MonoBehavior to start the job, then pass the handle to another MonoBehavior to check to see if the Job is complete?
     
  2. LennartJohansen

    LennartJohansen

    Joined:
    Dec 1, 2014
    Posts:
    2,394
    You could start a job in Update and the complete it later in LateUpdate. This will allow other parts of the game to use mainthread CPU while your job works.

    Lennart
     
    PrimalCoder likes this.
  3. vincismurf

    vincismurf

    Joined:
    Feb 28, 2013
    Posts:
    200
    Der, I can't belive I didn't think about LateUpdate. . . very clever solution!
     
  4. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    6,364
    the doc examples will be rewritten to reflect proper pattern... right?
     
  5. recursive

    recursive

    Joined:
    Jul 12, 2012
    Posts:
    669
    Another thing I don't think many people have messed with (because there's almost no docs on it) is the new customizable callback order, which if I believe from what I recall may allow a programmer to define new callbacks.

    So it could eventually be that you could:
    1. Define an "PreUpdate()" callback where you kick off your job
    2. If the job's IsComplete flag is true, integrate it in update.
    3. Force Complete() in LateUpdate(), ready or not, and integrate the result if it wasn't already integrated.
    4. In LateUpdate() (or the most relevant late-frame call, could be something for rendering) utilize the results.
    Or create your own callback order entirely. I'll have to look up where I saw it.

    EDIT:
    It was the Experimental PlayerLoop feature, which allows you some more fine-grained control over the primary callbacks than even Script Execution Order.
     
    Last edited: Feb 1, 2018
    ERayder, Kamyker, vincismurf and 2 others like this.
  6. Nyanpas

    Nyanpas

    Joined:
    Dec 29, 2016
    Posts:
    406
    For parallell job handling you would want async support so that a scheduled job can finish when it has raught its criteria for being finished (especially while the game is running). It seems that if you can listen for IsComplete, you can schedule the job to complete when it is finished and restart (or even resume if that is possible at some point) if needed. Forcing is not good as it can break completion.
     
  7. Trinary

    Trinary

    Joined:
    Jul 26, 2013
    Posts:
    395
    I, too, find this strange and confusing.
     
  8. Job system isn't for async operations, it's for threading your main game logic and calculations. Which means you don't only feed it with stuff what you will need later at some point, but with things you need for the frame itself, but calculations can be do parallel.
    This is why it's not async and it has a forced end point. At some point you need to process the results on the main thread in one form or another.

    For async operations you can use the proper C# classes.
     
  9. Nyanpas

    Nyanpas

    Joined:
    Dec 29, 2016
    Posts:
    406
    But letting threads finish when they need to instead of forcing them to finish is the definition of asynchronous.
     
  10. Kamyker

    Kamyker

    Joined:
    May 14, 2013
    Posts:
    1,091
    Interesting, didn't know there's Early Update. It's no longer experimental in 2019.3.

    Not really needed instead of:
    Code (CSharp):
    1. await jobHandle;
    just use:
    Code (CSharp):
    1. if ( !scaleJob.IsCompleted )
    2.     return;
    3. else
    4.     scaleJob.Complete();
    Prob best approach:
    Code (CSharp):
    1. EarlyUpdate():
    2. //schedule jobs
    3. dependentCompleted = false;
    4.  
    5. Update():
    6. if ( jobHandle.IsCompleted )
    7. {
    8.     jobHandle.Complete();
    9.     dependentCompleted = true;
    10.     RunDependentMainThread();
    11. }
    12.  
    13. LateUpdate():
    14. if(!dependentCompleted)
    15. {
    16.     jobHandle.Complete();
    17.     RunDependentMainThread();
    18. }
    JobHandle.IsCompleted is little weird, .Complete() still have to be called even when IsCompleted is true. It could be replaced with:
    Code (CSharp):
    1. enum JobHandleStatus
    2. {
    3.     Running
    4.     AwaitingCompletion
    5.     Completed
    6. }
    Made wrapper struct that implements it:
    Code (CSharp):
    1. public struct JobHandleExtended
    2. {
    3.     JobHandle handle;
    4.     public JobHandleStatus status;
    5.  
    6.     public JobHandleExtended( JobHandle handle ) : this()
    7.     {
    8.         this.handle = handle;
    9.         //by default status is Running
    10.     }
    11.  
    12.     public JobHandleStatus Status
    13.     {
    14.         get
    15.         {
    16.             if ( status == JobHandleStatus.Running && handle.IsCompleted )
    17.                 status = JobHandleStatus.AwaitingCompletion;
    18.  
    19.             return status;
    20.         }
    21.     }
    22.  
    23.     public void Complete()
    24.     {
    25.         handle.Complete();
    26.         status = JobHandleStatus.Completed;
    27.     }
    28.  
    29.     public static implicit operator JobHandle( JobHandleExtended extended )
    30.     {
    31.         return extended.handle;
    32.     }
    33.  
    34.     public static implicit operator JobHandleExtended( JobHandle handle )
    35.     {
    36.         return new JobHandleExtended( handle );
    37.     }
    38. }
    Code from the top with it:
    Code (CSharp):
    1. EarlyUpdate():
    2. //schedule jobsExtended
    3.  
    4. Update():
    5. if ( jobHandle.Status == JobHandleStatus.AwaitingCompletion)
    6. {
    7.     jobHandle.Complete();
    8.     RunDependentMainThread();
    9. }
    10.  
    11. LateUpdate():
    12. if ( jobHandle.Status != JobHandleStatus.Completed)
    13. {
    14.     jobHandle.Complete();
    15.     RunDependentMainThread();
    16. }
     
    Last edited: Feb 11, 2020
    NotaNaN, chadfranklin47 and Nyanpas like this.
  11. Kamyker

    Kamyker

    Joined:
    May 14, 2013
    Posts:
    1,091
    Found a bug in 2019.2.17. No idea if it's also in 2019.3

    Scheduled job won't run until calling jobHandle.IsComplete. So instantly after scheduling I have to check IsComplete to force start of it.

    From post above it didn't make sense to try-check in Update if it's completed, PreLateUpdate (and PostLateUpdate) makes more sense

    Code (CSharp):
    1. JobHandleExtended jobHandle;
    2.  
    3. void EarlyUpdate()
    4. {
    5.     jobHandle = myJob.Schedule();
    6.     var forceRun = jobHandle.Status; //force run here, use IsComplete if not using JobHandleExtended
    7. }
    8.  
    9. void PreLateUpdate()
    10. {
    11.     if ( jobHandle.Status == JobHandleStatus.AwaitingCompletion)
    12.     {
    13.         jobHandle.Complete();
    14.         RunDependentMainThread();
    15.     }
    16. }
    17.  
    18. void PostLateUpdate()
    19. {
    20.     if ( jobHandle.Status != JobHandleStatus.Completed)
    21.     {
    22.         jobHandle.Complete();
    23.         RunDependentMainThread();
    24.     }
    25. }
    Jobs and multithreading seems like a completely new and powerful world. My custom FFT replacement (that can also process non-realtime audio) for AudioSource.GetSpectrumData runs faster (0.33ms vs 0.24ms-0.15ms) but more importantly almost completely on background thread. Main thread comparison is 0.33ms vs less than 0.03ms!

    Here's how it looks:

    Scheduling jobs (profiler sometimes shows EarlyUpdate in previous frame):


    Jobs running in the background (in this example they all complete during Physics.Processing), some of them are multithreaded, main code singlethreaded:


    Sync back to main thread and use data in game:
     
    Last edited: Feb 11, 2020
    PrimalCoder and Nyanpas like this.
  12. thebanjomatic

    thebanjomatic

    Joined:
    Nov 13, 2016
    Posts:
    36
    From the C# Job System documentation:
     
    PrimalCoder and Nyanpas like this.
  13. Kamyker

    Kamyker

    Joined:
    May 14, 2013
    Posts:
    1,091
    Thanks, I guess that one goes to PreUpdate that happens after EarlyUpdate.
     
  14. Nyanpas

    Nyanpas

    Joined:
    Dec 29, 2016
    Posts:
    406
    Quantum computing is already here, it seems.
     
  15. Kamyker

    Kamyker

    Joined:
    May 14, 2013
    Posts:
    1,091
    Found a weird issue/bug:

    Seems like
    UnityEngine.LowLevel.PlayerLoop.SetPlayerLoop( );
    has one frame delay. My disposed NativeArray in OnDestroy/OnDisable throws error that it's being accessed next frame (in PostLateUpdate that should be removed already).

    Workaround fix:
    Code (CSharp):
    1. private void PostLateUpdate()
    2. {
    3.     if ( this != null ) //this is current MonoBehaviour
    4.     {
    5.  
    6.     }
    7. }