有一個技巧叫做「延遲結果蒐集」,可以避免主執行緒被迫要等待 Job 完成。
正常的 Job 寫法,通常循環會長這樣:
Loop()
{
    DispatchJob() // 發派 Job
    GatherResult() // 蒐集結果
    DoOtherStuffs()
}
合理吧?但這整個流程要在一幀內執行完成,也代表說 GatherResult 必須要阻止後續所有操作,等到這個 Job 先完成才行。也就代表說,真正的流程會更像這樣:
Loop()
{
    DispatchJob() // 發派 Job
    // 等待 Job 完成
    GatherResult() // 蒐集結果
    DoOtherStuffs()
}
藉由延遲結果蒐集的概念,我們可以在下一幀的開頭再蒐集結果,也就是:
Loop()
{
    // 希望 Job 已經完成了
    GatherResult() // 蒐集結果
    DispatchJob() // 發派 Job
    // 不需要等 Job 完成
    DoOtherStuffs()
}
在這個一幀的延遲下,就可以讓 Job 的執行能非同步化,理想上啦。也可以再進一步調整成,只有在計算保證完成時才蒐集結果,這樣就可以完全彌平等待時間:
Loop()
{
    if(jobDone) // 用非同步的方式等待工作完成
        GatherResult() // 蒐集結果
    if(!jobDispatched)
        DispatchJob() // 發派 Job
    // 不需要等 Job 完成
    DoOtherStuffs()
}
不過基於某些理由,當時我在研究怎樣撰寫可規模化的 AI 時,試著實作卻會收到 Unity 的一些錯誤訊息阻止我。當時實作多執行緒操作後,效能也大幅強化了,我就暫時不管這件事了。
不過最近閱讀一篇關於怎樣用 Job 來非同步地合併模型的範例,並且意識到有一種更直覺的方式可以實作延遲結果蒐集,讓我可以用「發派 Job→蒐集結果」的順序形式處理。
於是我使用 Coroutine 來實作:
Coroutine()
{
    JobHandle handle = DispatchJob()
    while(!handle.IsComplete)
        yield return null;
    handle.Complete();
    GatherResult();
}
在這樣的修改後,我的敵人管理流程可以在不需要被 Job 卡住的情況下輕鬆更新 100 個敵人。


