協(xié)程
協(xié)程的執(zhí)行方式與其他腳本代碼不同。大多數(shù)腳本代碼只顯示在性能跟蹤內(nèi)的位于特定 Unity 回調(diào)調(diào)用下某一個位置。但是,協(xié)程的 CPU 代碼總是出現(xiàn)在跟蹤內(nèi)的兩個位置。
協(xié)程中的所有初始代碼(從協(xié)程方法的開始一直到第一次暫停)將出現(xiàn)在跟蹤過程中任何啟動協(xié)程的位置。通常出現(xiàn)在調(diào)用?StartCoroutine
?方法的位置。從 Unity 回調(diào)(例如返回?IEnumerator
?的?Start
?回調(diào))生成的協(xié)程首先出現(xiàn)在各自的 Unity 回調(diào)中。
協(xié)程代碼的所有其余部分(從第一次恢復(fù)一直到完成執(zhí)行)將顯示在 Unity 主循環(huán)內(nèi)出現(xiàn)的?DelayedCallManager
?行中。
要了解為什么會發(fā)生這種情況,請思考協(xié)程實(shí)際上是如何執(zhí)行的。
協(xié)程由 C# 編譯器自動生成的類實(shí)例提供支持。此對象用于跟蹤單個方法(對程序員而言)的多次調(diào)用之間的協(xié)程狀態(tài)。因?yàn)閰f(xié)程中的局部作用域變量必須在?yield
?調(diào)用中保持一致,所以這些局部作用域變量將被保存到上一級的生成的它們的類中,從而保證在協(xié)程的存活期內(nèi)保留在堆上的地址分配。該對象還會跟蹤協(xié)程的內(nèi)部狀態(tài):它會記住協(xié)程暫停后必須從代碼中的哪一點(diǎn)恢復(fù)。
因此,啟動協(xié)程引起的內(nèi)存壓力等于固定開銷成本加上其局部變量的消耗。
啟動協(xié)程的代碼將構(gòu)造并調(diào)用此對象,然后 Unity 的?DelayedCallManager
?在每當(dāng)滿足協(xié)程的暫停條件時再次調(diào)用此對象。由于協(xié)程通常在其他協(xié)程之外啟動,因此它們的執(zhí)行成本將分擔(dān)到上述兩個位置。

在上面的截屏中可以看到這種情況,其中的?DelayedCallManager
?正在恢復(fù)幾個不同的協(xié)程:PopulateCharacters
、AsyncLoad
?和?LoadDatabase
?是其中需要注意的協(xié)程。
盡可能將一系列操作壓縮到最少數(shù)量的協(xié)程中。雖然嵌套的協(xié)程非常有利于確保代碼的條理性和進(jìn)行維護(hù),但協(xié)程跟蹤對象本身會導(dǎo)致產(chǎn)生更高的內(nèi)存開銷。
如果一個協(xié)程幾乎每幀都運(yùn)行并且在長時間運(yùn)行操作中不會暫停,那么用?Update
?或?LateUpdate
?回調(diào)來替換該協(xié)程通常更合理一些。例如長時間運(yùn)行或無限循環(huán)的協(xié)程。
禁用對象時,協(xié)程不會停止,只有明確銷毀對象時才會停止。對象禁用時允許協(xié)程仍然運(yùn)行,如果需要,可再次啟用對象。調(diào)用 Destroy(this) 會立即觸發(fā)?OnDisable
?并會處理協(xié)程。最后,在幀的末尾調(diào)用?OnDestroy
。
必須注意的是,協(xié)程不是線程。在協(xié)程內(nèi)運(yùn)行的同步操作仍然在主線程上執(zhí)行。如果需要減少主線程上花費(fèi)的 CPU 時間,與任何其他腳本代碼中一樣,在協(xié)程中避免阻塞操作同樣很重要。
在處理長時間異步操作(例如等待 HTTP 傳輸、資源加載或文件 I/O 完成)時,最適合使用協(xié)程。