<ruby id="9ue20"></ruby>

  1. 
    

      国产午夜福利免费入口,国产日韩综合av在线,精品久久人人妻人人做精品,蜜臀av一区二区三区精品,亚洲欧美中文日韩在线v日本,人妻av中文字幕无码专区 ,亚洲精品国产av一区二区,久久精品国产清自在天天线
      網易首頁 > 網易號 > 正文 申請入駐

      UE5多線程|TaskGraph

      0
      分享至


      【USparkle專欄】如果你深懷絕技,愛“搞點研究”,樂于分享也博采眾長,我們期待你的加入,讓智慧的火花碰撞交織,讓知識的傳遞生生不息!

      這是侑虎科技第1932篇文章,感謝作者南京周潤發供稿。歡迎轉發分享,未經作者授權請勿轉載。如果您有任何獨到的見解或者發現也歡迎聯系我們,一起探討。(QQ群:793972859)

      作者主頁:

      https://www.zhihu.com/people/xu-chen-71-65

      TaskGraph是線程池的進階,能讓任務之間產生依賴,上層可以方便地指定這種依賴。各任務的依賴關系就形成了“圖”。

      除了線程池,TaskGraph還可以管理GrameThread、RenderThread等獨立線程的調度,是UE中最復雜,功能最全面的多線程調度框架了。

      典型場景

      UE的多線程GC是TaskGraph的一個典型場景,需要把一個大的Array分割成若干小的Array,然后分到多個線程處理,GameThread需要等這些線程都處理完了,再執行以后的任務。代碼如下:


      注意最后的ParallelFor,把多線程處理封裝成并行For行為,分發到多個線程,然后等待多線程執行結束。

      如果用普通線程實現這個功能,需要手動用FEvent實現等待,要寫一些特化代碼。

      一、使用TaskGraph

      1. Gamethread Tick

      最常見的GameThread World Tick,就是由TaskGraph驅動的,因為GameThread也由TaskGraph管理,我們寫的Actor::Tick,Component::Tick都在這里執行。Tick函數本身可以包裝到TGraphTask里,然后用WaitUntilTasksComplete函數執行所有Task。


      2. Async函數

      Async函數可以指定EAsyncExecution::TaskGraph,讓任務在TaskGraph線程池中執行。還能指定EAsyncExecution::TaskGraphMainThread,讓一些短時間任務在主線程執行。


      3. WaitUntilTasksComplete

      如果需要發出一些異步任務,然后等待執行結束,可以手動構造FGraphEventArray,然后調用WaitUntilTasksComplete等待執行完畢,這里能體現TaskGraph的調度。


      二、TaskGraph線程池

      TaskGraph包含了線程池功能,不妨首先看線程池部分是如何實現的,這也比較好切入。類似FQueuedThreadPoolBase結構,TaskGraph的線程池有FTaskGraphInterface、FScheduler、FThread、TGraphTask和TAsyncGraphTask。

      1. FTaskGraphInterface

      FTaskGraphInterface是TaskGraph的管理類,是個單例,本身也是Interface,一些重要功能由子類實現。

      接口

      • Startup:初始化TaskGraph。

      • Shutdown:關閉TaskGraph。

      • AttachToThread:把一個獨立線程添加到TaskGraph中,比如GameThread和RenderThread。

      • WaitUntilTasksComplete:讓一些線程運行若干任務,并在當前線程等待這些任務都執行完。

      • TriggerEventWhenTasksComplete:當若干任務執行完,觸發一個Fevent。

      • ProcessThreadUntilIdle:讓一個NameThread一直處理自己的TaskQueue,直到執行完所有Task。

      子類

      FTaskGraphCompatibilityImplementation

      UE5的新TaskGraph子類,實現了TaskGraph的核心功能,不包含任務依賴功能,任務依賴由task實現。

      成員

      • uint32 PerThreadIDTLSSlot:TaskGraph用FWorkerThread結構體管理線程,每個線程在自己的TLS變量中存儲指向FWorkerThread結構的指針。

      • Int32 NumNamedThreads:Named線程數量。

      • Int32 NumWorkerThreads:Worker線程數量。

      • Int32 NumBackgroundWorkers:BackgroudWorker數量。

      • Int32 NumForegroundWorkers:ForegroundWorker數量。

      • TArray NamedThreads:管理了所有NamedThread。

      FTaskGraphImplementation:舊TaskGraph子類實現,不看了。

      2. FScheduler

      FScheduler用于創建、管理Workder線程,以及把Task分派給Worker線程。

      成員

      • TArray > WorkerThreads:工作線程。

      • TAlignedArray WorkerLocalQueues:WorkerThread對應的Task。

      • TAlignedArray WorkerEvents:WorkerThread對應的Event。

      • EThreadPriority WorkerPriority:工作線程優先級。

      • EThreadPriority BackgroundPriority:Background WorkerThread優先級。

      • FSchedulerTls::FQueueRegistry QueueRegistry:全局任務隊列。

      方法

      • StartWorkers:創建WorkerThreads和Event等。

      • StopWorkers:執行完所有Task,然后銷毀WorkerThreads。

      • TryLaunch:在WorkerThreads上執行Task。

      • WakeUpWorker:通過Event Trigger喚醒WorkerThreads。

      3. FThread

      TaskGraph創建的WorkerThread,使用FThread來管理,它是操作系統中一個線程的表示,封裝了一個FThreadImpl。

      方法

      Join:最主要的方法,等待線程執行完畢。

      成員

      TSharedPtr Impl:實際的Frunnable。

      4. FThreadImpl

      FThread的具體實現,繼承自Frunnable。

      方法

      Run:調用了成員ThreadFunction。

      成員

      • TUniqueFunction ThreadFunction:線程要執行的函數,就是WorkerMain。

      • TUniquePtr RunnableThread:對應的FRunnableThread對象。

      5. TGraphTask

      TaskGraph系統中管理的Task,不直接調用用戶提供的Task函數,而是把函數封裝成一個user defined task,存儲在其中。

      成員

      • TAlignedBytes TaskStorage:存儲的user defined task,類型由模板指定。

      • FGraphEventRef Subsequents:存儲哪些GraphTask以我們為前置。

      方法

      • CreateTask:創建一個新GraphTask。

      • ExecuteTask:執行Task。

      • SetupPrereqs:設置Task前置。

      6. TAsyncGraphTask

      屬于user defined task,是UE為實現Async函數而創建的類。

      成員

      • TUniqueFunction Function:用戶提供的Task方法。

      • LowLevelTasks::FTask TaskHandle:FSchedule中對應的FTask對象。

      方法

      DoTask:執行Function。

      7. FTask

      Scheduler中使用的最底層任務對象。

      成員

      • FTaskDelegate Runnable:封裝的Task函數對象。

      • FPackedDataAtomic PackedData:Priority,DebugName等信息。

      方法

      ExecuteTask:執行Task。

      借用其他博主畫的類圖,這張類圖畫的很好,但需要把其中的FTaskGraphImplementation類換成FTaskGraphCompatibilityImplementation:


      三、初始化Worker線程

      在PreInitPreStartupScreen函數中,會調用FTaskGraphInterface::Startup函數初始化TaskGraph,然后調用到Fscheduler::StartWorkers創建WorkerThreads。 參數NumberOfWorkerThreadsToSpawn與CPU核數有關,Windows平臺為總核數減2,估計一個留給GameThread,一個留給RenderThread。




      WorkerThreads分為ForegroundWorker和BackgroundWorker,線程優先級不一樣,分別是TPri_SlightlyBelowNormal和TPri_BelowNormal,ForegroundWorker默認只有兩個。最終的創建WorkerThreads代碼如下:


      對于每個WorkerThread,要創建三樣東西:

      • 首先創建一個屬于該WorkerThread的FSleepEvent,內部包含了WorkerThread當前狀態和對應的FEvent對象,用于管理WorkerThread的Sleep、Running等狀態轉換,存儲在WorkerEvents中。

      • 然后創建一個Local任務隊列,用于存儲Task,存在WorkerLocalQueues數組中。

      • 最后通過CreateWorker創建一個線程,用FThread包裝,存儲在WorkerThreads數組。線程函數是FScheduler::WorkerMain,主要任務從Task隊列中取出Task并執行。

      對于ForegroundWorker和BackgroundWorker,一些參數會有不同。

      除了專門的WorkerThread,GameThread也能作為WorkerThread使用,可以把一些Task指定到GameThread執行,具體會在下面介紹。

      1. 添加任務

      觀察Async函數,首先調用CreateTask,創建一個FConstructor對象,內部包裝一個TGraphTask實例。TGraphTask創建時可以指定前置Task,但Async函數的任務是輕量的異步任務,沒有前置,因此這里直接用NULL。TGraphTask接受模板參數TTask,這里為TAsyncGraphTask。



      TAsyncGraphTask

      TAsyncGraphTask是用戶自定義Task,可以把一個Lambda函數派發到WorkerThread或者GameThread上執行。

      DoTask函數


      GetDesiredThread函數,可以在構造函數中傳入想執行的線程。


      然后執行ConstructAndDispatchWhenReady,先構造一個TAsyncGraphTask實例,設置到TGraphTask.TaskStorage指針上。然后執行Setup函數,其中一些操作是GraphTask前置和后置相關的,先不管,最后會進入QueueTask函數,把任務添加到TaskGraph執行。


      注意到這里用了FConstructor作為Helper類,把難寫的TaskStorage原地構造包在里面,更易使用。

      FConstructor還有另一個函數ConstructAndHold,這可以先創建TGraphTask,但不執行,后面通過手動調用TGraphTask::Unlock執行,但這種用法不多。


      GraphTask也有一個優先級類型,為ETaskPriority,這里首先會根據GraphTask希望執行的線程類型,得到對應的TaskPriority,AnyThread對應的就是Normal。

      Task->GetTaskHandle()獲取了GraphTask內部的FTask對象,Init操作用于把Priority和封裝的Lambda函數參數賦值進去,初始化FTask對象。

      最后TryLaunch會進入FSchedule,把FTask加入到任務隊列中。



      任務隊列分為Thread Local和Global兩種,Async函數場景會加入Global,TaskGraph任務隊列特點是無鎖,即使多生產者,多消費者,也不需要加CriticalSection級別的鎖,只使用原子操作。關于無鎖任務隊列,會在下面專門介紹。

      WakeUpWorker后面再看。

      至此,用戶提供的Task已經被加入到任務隊列。

      2. 執行任務

      首先看創建Worker Trhead的線程函數WorkerMain:


      參數含義:

      • WorkerEvent:線程對應的SleepEvent,存在Scheduler數組中。

      • ExternalWorkerLocalQueue:存Task的LocalQueue,當前WorkerThread獨占,存在Scheduler數組中。

      • WaitCycles:線程短等待的YieldCycles,不同WorkerThread會有些差異,避免大家一起執行YieldCycles。

      • bPermitBackgroundWork:BackgroundWorker為true,ForegroundWorker為false。

      然后是一個大While循環,不斷從Task隊列中取Task執行,沒有Task則進入Sleep。這里涉及到一些細節,首先看到Worker隊列有很多種,然后線程也不是簡單的沒Task就進入Sleep,而是有更多狀態切換,以達到更好性能。


      先忽略Task隊列的細節,因為這涉及到無鎖隊列的實現,認為從一個邏輯上的隊列里取Task,進入TryExecuteTaskFrom函數。最終進入ExecuteTask函數,執行用戶提供的Task,返回值AnyExecuted表示是否執行了Task。


      Task處理完后不直接用WaitEvent進入Wait,TaskGraph里增加了一個Drowsing(休眠)狀態,總共有三個狀態,狀態通過FSleepEvent結構體維護,轉換邏輯在TrySleeping函數。

      Running:正在執行Task。

      Drowsing:隊列中Task剛執行完不久,執行WorkerSpinCycles次的主動YieldCycles函數,釋放一點CPU時間片,估計為了避免頻繁調用Wait和Trigger。進入Drowsing會把FSleepEvent加入SleepEventStack容器,認為已經處于不活躍狀態,需要通過WakeUpWorker調用從容器中移除,改回Running。

      Sleeping:一段時間的Drowsing狀態內沒有執行新的Task,調用FEvent.Wait,線程進入阻塞狀態。只有通過WakeUpWorker函數執行FEvent.Trigger后才能恢復執行,同時會把FSleepEvent從SleepEventStack中彈出,把狀態改回Running。

      狀態轉換圖如下:


      3. Task優先級

      游戲運行過程中會產生大量Task,UE支持為Task指定多個優先級,提供更細粒度的控制,雖然在Async函數里只提供了一種優先級。這里只討論Task在WorkerThread中執行的情況,GameThread和RenderThread執行Task另外再討論。

      Task優先級定義如下:


      真正有意義的是High、Normal、BackgroundHigh、BackgroundNormal和BackgroundLow五種,運行時會按照優先級維護多個隊列,按照優先級順序執行這些Task。

      但用戶不能直接指定Task的優先級。用戶自定義Task可以通過GetDesiredThread函數指定希望執行的線程、線程優先級、以及Background Task的優先級,最終會設置在TGraphTask的ThreadToExecuteOn屬性上。

      這個int32中嵌入了很多信息:


      ENamedThreads的組成如下,按比特位劃分了不同區域,具體也可看enum定義,這里過長不貼了。


      • ThreadId部分8位

      標識線程的ID,NamedThread下標從0開始,StatsThread=0,RHIThread=1,AudioThread=2,GameThread=3,AnyThread=0xff。

      • QueueIndex部分1位

      MainQueue=1,LocalQueue=2。

      • ThreadPriority部分2位

      指定不同線程優先級,也可以認為是Task的粗粒度優先級,NormalThreadPriority=0,HighThreadPriority=1,BackgroundThreadPriority=2。

      • TaskPriority部分1位

      用戶定義的Task細粒度優先級,僅對ThreadPriority=BackgroundThreadPriority時有效,把BackgroundThreadPriority再細分,NormalTaskPriority=0,HighTaskPriority=1。

      注意ThreadId的AnyThread選項,表示在任意Worker線程執行,但之前介紹過Worker線程分為ForgroundWorker和BackgroundWorker,它們線程優先級不同,Task具體在哪類Worker中執行,還是要看根據ENamedThreads得到的TaskPriority。

      多個枚舉可以組合,引擎提供了一些預置enum,目前并不是所有組合都支持,比如AnyHiPriThreadNormalTask和AnyHiPriThreadHiPriTask是等同的,只是先都定義了。

      以AnyBackgroundThreadNormalTask為例,該Task會在WorkerThread中執行,線程TaskPriority是BackgroundNormal,用戶定義TaskPriority是NormalTaskPriority。


      UE也提供了一些Helper函數,從中獲取信息:

      • GetThreadIndex

      • GetQueueIndex

      • GetTaskPriority

      • GetThreadPriorityIndex

      最終的TaskPriority和WorkerThread種類由ThreadPriority和用用戶定義TaskPriority共同決定,代碼在FTaskGraphCompatibilityImplementation::QueueTask中,整理的對應關系如下:


      TaskQueue也按照TaskPriority數量進行了劃分,各優先級有自己的容器。TaskQueue分為Thread Local LocalQueue和全局的OverflowQueues,定義如下,是個ETaskPriority::Count的數組:


      以OverflowQueues為例,添加Task代碼如下:


      取Task代碼如下,優先級從高到低遍歷:


      總結一下,TaskGraph提供線程池功能時執行流程圖如下,這里TAsyncGraphTask也可以換成我們自己寫的用戶Task,同樣使用TGraphTask ::CreateTask().ConstructAndDispatchWhenReady接口即可。


      四、TaskGraph管理NamedThread

      TaskGraph不僅可以創建WorkerThread執行任務,還能把GameThread、RenderThread等專用線程也納入管理,分派任務給線程執行。

      回顧FTaskGraphCompatibilityImplementation定義,其中包含了NameThreads容器,用一個FWorkerThread代表一個NamedThread。


      NamedThread線程ID定義如下,有RHIThread、AudioThrad、GameThread和RenderThread四個。


      1. FWorkerThread

      表示一個線程,包含相關信息,目前實現只用于NamedThread。

      成員

      • FTaskThreadBase*TaskGraphWorker:真正的TaskGraphWorker。

      • bool bAttached:NameThread是否被注冊到TaskGraph系統。

      2. FTaskThreadBase

      用于讓NamedThread有執行GraphTask的能力。

      成員

      • ENamedThreads::Type ThreadId:線程ID。

      • Uint32 PerThreadIDTLSSlot:FWorkerThread對象指針會被存儲到這個Slot對應的TLS中,這樣NamedThread就能取到它了。

      • TArray NewTasks:這個線程要執行的Task。

      • FWorkerThread*OwnerWorker:所有者FWorkerThread的指針。

      函數

      ProcessTasksUntilQuit

      • ProcessTasksUntilIdle:兩個都用于讓NameThreads不斷執行Task,直到線程Idle或者設置RequestQuit標記。

      • EnqueueFromThisThread:向線程添加GraphTask任務,當前執行的線程就是NamedThread。

      • EnqueueFromOtherThread:效果同上,當前執行線程不是NamedThread。

      • Run:內部執行ProcessTasksUntilQuit。

      3. FNamedTaskThread

      繼承自FTaskThreadBase,用于管理NamedTask。

      成員

      FThreadTaskQueue Queues[ENamedThreads::NumQueues]:存儲Task的隊列,分MainQueue和LocalQueue兩個。

      函數

      覆寫了ProcessTasksUntilQuit,ProcessTasksUntilIdle,EnqueueFromOtherThread。

      4. FThreadTaskQueue

      NamedTaskThread擁有的Task隊列。

      • FStallingTaskQueue StallQueue:包裝了兩個LockFreelist,對應High和Normal兩個優先級,NamedThread的Task只有這兩個優先級。

      • FEvent*StallRestartEvent:當線程執行完Task后,在該Event上等待

      五、創建FWorkerThread對象

      在TaskGraph Startup時,會根據NameThreads數量,創建對應的FWorkerThread對象,存儲在NamedThreads數組中。FWorkerThread初始化主要有兩個參數:一個是分配的TLS Slot,用來存它,另一個是FNamedTaskThread對象。


      六、GameThread注冊到TaskGraph

      當前線程調用AttachToThread函數可以把自己注冊到TaskGraph中,需要提供一個線程ID。

      這是GameThread的注冊方式,在Startup后就立即注冊了:


      接著執行到這里,先根據CurrentThread ID獲取到對應的TaskGraphWorker,然后調用InitializeForCurrentThread,該函數會把OwnerWorker存儲在PerThreadIDTLSSlot的TLS中。



      這樣就完成了注冊。

      其他幾個NamedThread也用同樣的方式注冊。

      七、向NameThread添加Task任務

      使用Async函數可以向GameThread添加Task,把參數設為EAsyncExecution::TaskGraphMainThread即可。往后的CreateTask等流程都相同,區別只在最后的QueueTask。


      這里傳入的InThreadToExecuteOn為GameThread,InCurrentThreadIfKnown沒有設置,默認為AnyThread,也可以工作。

      QueueToExecuteOn表示希望加在MainQueue還是LocalQueue,在外部可以設置。

      比較值得注意的GetCurrentThread函數,需要得到當前線程ID,用ENamedThreads表示。


      如果是NamedThread,已經設置了TLS,從中取出FWorkerThread指針,然后得到在NamedThreads中的偏移,就是ThreadId。

      如果是AnyThread,還會先嘗試獲取當前線程上的ActiveTask,然后獲取ThreadPriority和TaskPriority,一并返回。

      最后根據ThreadToExecuteOn和CurrentThreadId,調用EnqueueFromThisThread或EnqueueFromOtherThread,這兩個接口區別為前者是當前線程調用的,后者可以由其他線程調用,也可以由當前線程調用,多了一步線程喚醒操作。

      EnqueueFromThisThread把Task加到Queues容器中,QueueIndex決定是MainQueue還是LocalQueue,默認MainQueue,然后從之前的ThreadIdAndIndex里獲取到TaskPriority,決定加到內部的HighPriority還是NormalPriority Task容器。


      EnqueueFromOtherThread也會先把Task加入StallQueue,然后看是否有ThreadToStart,有則調用Trigger,喚醒線程。


      八、NamedThread執行Task

      以GameThread為例,看如何執行TaskGraph中的Task。

      GameThread每幀都會通過World::Tick函數,執行各種Actor的Tick,驅動游戲世界,而各種Tick函數又通過FTickTaskManager管理,背后再轉換成一個個TGraphTask,放到TaskGraph中執行。

      直接進入FTickTaskSequencer::ReleaseTickGroup函數,這里會執行一個TickGroup中全部的Tick,代碼如下:


      然后進入WaitUntilTasksComplete函數,執行這些Task。WaitUntilTasksComplete含義是等待這些Task執行完,方法為創建一個FReturnGraphTask,并把要等待的Task設為前置,FReturnGraphTask作用是把FNamedTaskThread.Queue.QuitForReturn設為true,讓TaskGraph執行完這些Task后就返回。

      WaitUntilTasksComplete


      之后執行到ProcessTasksUntilQuit和ProcessTasksNamedThread,不斷從Queue中取GraphTask并執行,直到執行了FReturnGraphTask,然后返回。



      我們之前通過Async函數向GameThread添加的Task,也是在這里從Queue中取出,然后被執行的。

      再借用一張圖,描述NamedThreads執行Task的過程:


      九、GraphTask的依賴關系

      TaskGraph區別于普通線程池的一大特點,就是GraphTask能存在前置依賴,這樣可以自定義Task的執行順序,多線程動畫、多線程GC等都是這樣實現的。

      GraphTask依賴關系需要解決兩個問題:

      • 如何組織Task,按照依賴順序執行這些Task;

      • 等待依賴的Task執行完成會可能造成線程休眠,如何喚醒線程。

      以多線程動畫更新為示例,看如何建立Task間依賴。動畫多線程更新可以把動畫的Update、Evaluate開銷都放到WorkerThread中,減輕GameThread負擔,當SkeletalMeshComponent多時尤為明顯。


      首先創建一個FParallelAnimationEvaluationTask,用來做動畫多線程Update和Evaluate,派發到WorkerThread上執行。然后創建一個FParallelAnimationCompletionTask,用來做動畫更新后的PostAnimEvaluation,在GameThread上執行,前置為FParallelAnimationEvaluationTask,這一切都發生在PrePhysics tick階段。

      簡單時序圖如下:


      1. GraphEvent

      這里Task依賴通過FGraphEventArray結構實現,而FGraphEventArray其實是一組FGraphEvent的引用,FGraphEvent是Task依賴的關鍵。


      GraphEvent可以理解為GraphTask相關的“事件”,GraphTask之間通過“事件”聯系。

      2. FGraphEvent

      包含了一系列后置Task,該GraphEvent是它們的觸發條件。

      成員

      • TClosableLockFreePointerListUnorderedSingleConsumer SubsequentList:后置Task,是無鎖鏈表。

      • FGraphEventArray EventsToWaitFor:該GraphEvent要等待的其他GraphEvent數組,其實只有一個元素,其他GraphEvent完成后,該EventGraph才會觸發,在DontCompleteUntil里設置。

      方法

      • AddSubsequent:添加一個后置Task。

      • DontCompleteUntil:提供一個前置GraphEvent,前置完成后自己才觸發。

      回顧一下TGraphTask的成員:

      • Subsequents:該GraphTask對應的FGraphEvent。

      • NumberOfPrerequistitesOutstanding:該GraphTask有多少個前置待執行。

      • ConstructAndDispatchWhenReady函數會返回GraphTask對應的GraphEvent,外部就能操作它了。

      3. CreateTask

      CreateTask方法可以接受Prerequistes參數,得到該GraphTask的前置,接著進入TGraphTask::SetupPrereqs函數。



      會通過AddSubsequent函數把自己添加到所有Prerequisties的后置里,然后會判斷Prerequisties是否都完成了,完成后才通過QueueTask把該GraphTask加到Task隊列里,等待執行,大部分情況都不會進入,需要等待前置。

      4. DispatchSubsequents

      在TGraphTask執行完后,會通過Subsequents對象執行DispatchSubsequents,讓其他依賴自己的Task執行。這里要分有無EventsToWaitFor的情況。

      無EventsToWaitFor:

      TGraphTask執行完后,就立即觸發完成事件,需要遍歷所有SubsequentList里的后置Task,調用ConditionalQueueTask,如果后置的所有前置都已被觸發,就調用QueueTask,把自己加入Task隊列,等待執行。



      有EventsToWaitFor:

      有時候TGraphTask自己完成了,但不想立即觸發事件,還想等待另一個GraphEvent完成后再觸發,

      比如多線程動畫更新里的TickFunction函數,對應的事件要等到TickCompletionEvent完成后再觸發。相當于TickFunction Task已經在執行了,但還想給它添加前置一樣。



      這個操作通過增加一個NullGraphTask完成,這個Task繼承了自己的Subsequents,并且把EventsToWaitFor作為自己前置,本身的ExecuteTask并沒有任何邏輯,只是為了觸發原本的后置Task。


      回到動畫多線程更新的例子,用圖表展示執行流程和GraphTask、GraphEvent的工作過程:


      這只是簡單的TaskGraph依賴關系,當然可以自己組合出一些多前置,多后置的TaskGraph依賴,背后原理是一樣的。

      十、NamedThread Sleep/喚醒

      多線程動畫例子中,如果FParallelAnimationEvaluationTask執行時間過長,GameThread已經把PrePhysics階段的所有Tick都執行完了,就會進入Sleep狀態,等FParallelAnimationEvaluationTask執行完后再喚醒GameThread繼續執行。

      1. 進入Sleep

      GameThread在Tick時會執行ProcessTasksNamedThread,While循環從Queue中獲取下一個Task,執行到ReturnTask之前都不會退出,如果取不到Task了,說明需要等其他線程執行完前置Task,那么GameThrad自身會在這個Queue的StallRestartEvent上Wait,進入Sleep狀態。


      StallQueue有設計,可以用一個uint64記錄線程是否在StallRestartEvent上Wait,目前支持一個線程,因為StallQueue也是單個FNamedTaskThread對象獨有的,但看代碼是想設計成支持26個線程。

      看下StallQueue的Pop函數:


      當沒能獲取到新的Task時,表示當前Thread要進入Wait了,會修改MasterState,記錄下這個線程。MasterState是一個巧妙的uint64位結構,可以同時記錄多線程訪問信息和等待的線程信息,結構如下:


      Counter用于多線程保護,每次進Pop和Push都會加1,在修改Ptrs前都會比較一下Counter是否和進函數時相同,防止Pop和Push在不同線程被執行,導致判斷不正確。

      當Counter判斷通過,就會把Ptrs的MyThread位設置為1,表示這個線程在StallRestartEvent上Wait了,目前MyThread固定為0。

      2. 喚醒

      當調用EnqueueFromOtherThread添加Task后,會判斷線程是否在Sleep狀態,然后執行StallRestartEvent.Trigger()喚醒線程,繼續執行。


      StallQueue的Push函數如下:


      會從MasterState中尋找Ptrs里被設置為1的位,表示哪些線程在上面Wait,得到ThreadToWake,外層函數再對其調用Trigger喚醒。

      十一、一些Task同步函數

      當發出多個Task,分派到不同線程執行后,邏輯上通常希望能對這些Task做些同步操作,比如在一個時間點等待這些Task都執行完,或者像動畫多線程例子那樣給TickFunction加WaitEvent,TaskGraph框架提供了多種這樣的函數。

      1. TaskGraph接口

      • WaitUntilTasksComplete(Tasks)

      等待多個GraphEvent執行完,內部做法是增加一個FReturnTask,把傳入的Tasks作為其前置,然后調用ProcessThreadUntilRequestReturn。

      比如如下代碼:



      • ProcessThreadUntilIdle

      在NamedThread上調用,阻塞執行當前Queue里的所有Task,直到完成。

      • ProcessThreadUntilRequestReturn

      與ProcessThreadUntilIdle類似,只是需要預先添加一個ReturnTask任務。

      ProcessThreadUntilIdle和ProcessThreadUntilRequestReturn兩個函數通常只有引擎會使用,項目代碼里感覺沒這個需求。

      2. GraphEvent接口

      • DontCompleteUntil

      GraphEvent的函數,之前動畫藍圖例子已介紹過,會給當前GraphEvent設置另一個Event作為EventsToWaitFor,在EventsToWaitFor觸發后,才觸發當前的GraphEvent。

      流程圖見上面。

      • Wait

      內部調用了TaskGraph的WaitUntilTasksComplete接口,把自己作為參數傳入,效果與WaitUntilTasksComplete相同。

      文末,再次感謝南京周潤發 的分享, 作者主頁:https://www.zhihu.com/people/xu-chen-71-65, 如果您有任何獨到的見解或者發現也歡迎聯系我們,一起探討。(QQ群: 793972859 )。


      近期精彩回顧




      特別聲明:以上內容(如有圖片或視頻亦包括在內)為自媒體平臺“網易號”用戶上傳并發布,本平臺僅提供信息存儲服務。

      Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.

      相關推薦
      熱點推薦
      重慶奔馳撞母女續:肇事醫生極少自己開車,丈夫到現場全程未發聲

      重慶奔馳撞母女續:肇事醫生極少自己開車,丈夫到現場全程未發聲

      童童聊娛樂啊
      2026-05-14 00:37:51
      四川武警營門推哨兵后續,大家都搞錯了罪名,她面對的不是襲警罪

      四川武警營門推哨兵后續,大家都搞錯了罪名,她面對的不是襲警罪

      音樂時光的娛樂
      2026-05-13 00:52:15
      揚言“闖過去,我能搞定”!多次在深圳違法,舒某、裴某被判有期徒刑

      揚言“闖過去,我能搞定”!多次在深圳違法,舒某、裴某被判有期徒刑

      南方都市報
      2026-05-13 19:13:31
      上海市市場監督管理局:一糖果制品被檢出高劑量西地那非

      上海市市場監督管理局:一糖果制品被檢出高劑量西地那非

      財經網
      2026-05-13 13:02:05
      痛失兩個兒子,笑著接受采訪、侃侃而談!獲獎女作家的淡定引爭議

      痛失兩個兒子,笑著接受采訪、侃侃而談!獲獎女作家的淡定引爭議

      火山詩話
      2026-05-12 06:56:54
      國際足聯主動妥協,五折甩賣世界杯版權,央視為何拒不買單

      國際足聯主動妥協,五折甩賣世界杯版權,央視為何拒不買單

      璀璨幻行者
      2026-05-10 22:09:58
      楚雖三戶、亡秦必楚!"三戶"指的是哪三戶?說出來你可能不信

      楚雖三戶、亡秦必楚!"三戶"指的是哪三戶?說出來你可能不信

      顧史
      2026-05-13 09:30:59
      王室徹底被激怒!哈里為梅根向王室提無理要求,威廉王子不再留情面

      王室徹底被激怒!哈里為梅根向王室提無理要求,威廉王子不再留情面

      小魚愛魚樂
      2026-05-13 16:44:55
      我國都有哪些常見毒蛇?哪種蛇最毒?盤點我國十大毒蛇,第一名致死率超65%

      我國都有哪些常見毒蛇?哪種蛇最毒?盤點我國十大毒蛇,第一名致死率超65%

      農夫也瘋狂
      2026-05-13 11:40:43
      英媒:引起不滿,阿森納向員工收取859英鎊歐冠決賽差旅費

      英媒:引起不滿,阿森納向員工收取859英鎊歐冠決賽差旅費

      懂球帝
      2026-05-14 01:06:11
      日本殺人犯逃亡后整容,因太帥了走紅,大量女粉絲為其應援求情

      日本殺人犯逃亡后整容,因太帥了走紅,大量女粉絲為其應援求情

      莫地方
      2026-05-12 00:45:03
      果然是經濟強省!浙江縣域第一高樓,高約300米!

      果然是經濟強省!浙江縣域第一高樓,高約300米!

      GA環球建筑
      2026-05-13 12:56:58
      夜景中的美女,身材真好

      夜景中的美女,身材真好

      藍色海洋009
      2026-05-13 17:06:46
      26屆新秀天賦有多變態?體測數據出爐,狀元大熱全員拉滿!

      26屆新秀天賦有多變態?體測數據出爐,狀元大熱全員拉滿!

      籃球小布丁
      2026-05-14 03:09:23
      22年不算長!收起嘲笑,BIG6聯賽冠軍荒,槍手竟然是最短的

      22年不算長!收起嘲笑,BIG6聯賽冠軍荒,槍手竟然是最短的

      濤哥侃球
      2026-05-13 17:31:47
      新冠后遺癥對人體的最大影響,很多人深受其害,有些人還不自知

      新冠后遺癥對人體的最大影響,很多人深受其害,有些人還不自知

      呼吸科大夫胡洋
      2026-02-22 11:39:12
      谷歌用10年最大更新把車機屏幕逼成幾何題:異形屏終于能"滿血"顯示了

      谷歌用10年最大更新把車機屏幕逼成幾何題:異形屏終于能"滿血"顯示了

      固件更新中
      2026-05-13 07:17:15
      在岸人民幣兌美元較周二夜盤收盤漲52點

      在岸人民幣兌美元較周二夜盤收盤漲52點

      財聯社
      2026-05-14 03:10:05
      太意外!央視硬剛國際足聯:世界杯天價泡沫,在中國徹底碎了

      太意外!央視硬剛國際足聯:世界杯天價泡沫,在中國徹底碎了

      魏家東
      2026-05-11 09:42:19
      48022桌需退款,共110萬元!知名連鎖餐飲突然道歉并退錢,涉上海等全國24家門店,都存在這個問題

      48022桌需退款,共110萬元!知名連鎖餐飲突然道歉并退錢,涉上海等全國24家門店,都存在這個問題

      新民晚報
      2026-05-13 19:41:58
      2026-05-14 07:59:00
      侑虎科技UWA incentive-icons
      侑虎科技UWA
      游戲/VR性能優化平臺
      1575文章數 987關注度
      往期回顧 全部

      科技要聞

      阿里年營收首破萬億,AI終于不再是畫大餅

      頭條要聞

      中東戰火燒痛印度 莫迪六天訪五國要外交“救國”

      頭條要聞

      中東戰火燒痛印度 莫迪六天訪五國要外交“救國”

      體育要聞

      14年半,74萬,何冰嬌沒選那條更安穩的路

      娛樂要聞

      白鹿掉20萬粉,網友為李晨鳴不平

      財經要聞

      美國總統特朗普抵達北京

      汽車要聞

      C級純電轎跑 吉利銀河"TT"申報圖來了

      態度原創

      本地
      旅游
      家居
      公開課
      軍事航空

      本地新聞

      用蘇繡的方式,打開江西婺源

      旅游要聞

      泰國擬縮短93國游客免簽停留期限,從60天減少至30天

      家居要聞

      內在自敘,無域有方

      公開課

      李玫瑾:為什么性格比能力更重要?

      軍事要聞

      美以伊戰爭期間以總理密訪阿聯酋

      無障礙瀏覽 進入關懷版 主站蜘蛛池模板: 丁香精品无码| 综合亚洲网| 久热re这里精品视频在线6| 亚洲精品AV久久看| 夜夜未满十八勿进的爽爽影院| 免费观看添你到高潮视频| 国精品午夜福利视频不卡| 少妇50p| 啊灬啊灬啊灬快灬高潮了电影片段 | 中文文字幕文字幕亚洲色| 人妻2| 日韩内射美女人妻一区二区三区| 极品美女高潮呻吟国产剧情| 人妻一区二区三区久久| 欧美xxxx黑人又粗又大| 国产精品一区在线免费看| 亚洲一区久久蜜臀av| 首页 - 91n| 中文字幕人妻精品免费| 久久无码av一区二区三区电影网| 777色婷婷| 深夜精品免费在线观看| 亚洲AV无码东方伊甸园| 九九久久国产精品免费热6| 亚洲AV成人无码久久精品色欲| 久久香蕉超碰97国产精品| 国产稚嫩高中生呻吟激情在线视频| 欧美综合人人做人人爱| 国产免费午夜福利在线观看| 国产精品秘?国产A级| 日本伊人色综合网| 日本一区三区在线视频| 在线看国产精品三级在线| 动漫精品专区一区二区三区| 99久久无色码中文字幕| 午夜大片免费男女爽爽影院| 无套内射极品少妇chinese| 蜜桃av一区二区高潮久久精品| 亚洲欧美一区二区三区国产精| 色综合色国产热无码一| 九九热在线精品免费视频 |