UE4 TaskGraph源碼分析

TaskGraph Library

TaskGraph用于實現(xiàn)將將多個Taskes放入多個線程執(zhí)行,并且可以設(shè)定這些Task之間的依賴励稳。引擎很多模塊用到了它∮蚕耍現(xiàn)在我們就來解讀一下它的實現(xiàn)杆逗,文章后部分再進行使用案例分析预茄。
源碼路徑:

  • Engine\Source\Runtime\Core\Public\Async\TaskGraphInterfaces.h
  • Engine\Source\Runtime\Core\Private\Async\TaskGraph.cpp

關(guān)鍵類

  1. FTaskGraphInterface
    Interface tot he task graph system.
  2. FBaseGraphTask
    Base class for all tasks.
  3. FGraphEvent
    A FGraphEvent is a list of tasks waiting for something. These tasks are call the subsequents. A graph event is a prerequisite for each of its subsequents. Graph events have a lifetime managed by reference counting.
  4. template< typename TTask> class TGraphTask
    Embeds a user defined task, as exemplified above, for doing the work and provides the functionality for setting up and handling prerequisites and subsequents.
  5. FReturnGraphTask
    a task used to return flow control from a named thread back to the original caller of ProcessThreadUntilRequestReturn.
  6. FNullGraphTask
    a task that does nothing. It can be used to "gather" tasks into one prerequisite.
  7. FTriggerEventGraphTask
    a task that triggers an event(operating system Event object)
  8. FSimpleDelegateGraphTask
    for simple delegate based tasks. This is less efficient than a custom task, doesn't provide the task arguments, doesn't allow specification of the current thread, etc.
  9. FDelegateGraphTask
    class for more full featured delegate based tasks. Still less efficient than a custom task, but provides all of the args.
  10. FFunctionGraphTask
    Task class for lambda based tasks.
  11. FCompletionList
    List of tasks that can be "joined" into one task which can be waited on or used as a prerequisite.

經(jīng)過分析源碼狠怨,得出如下設(shè)計思路:

  1. 一個TaskGraph對象會依賴多個GraphEvent對象, 也就是說該TaskGraph對象在收到所有先決事件觸發(fā)后坑资,才能執(zhí)行任務(wù);
  2. 一個TaskGraph對象執(zhí)行任務(wù)后运敢,會觸發(fā)它的GraphEvent, GraphEvent會嘗試喚醒依賴于它的Task校仑。
  3. FTaskGraphInterface的實現(xiàn)負責(zé)執(zhí)行TaskGraph的任務(wù)。
    綜上所述, 整個系統(tǒng)分兩個部分:
  • TaskGraph和GraphEvent組成了Task依賴網(wǎng)絡(luò)(當(dāng)然不能出現(xiàn)循環(huán)依賴);
  • FTaskGraphInterface的實現(xiàn)負責(zé)執(zhí)行TaskGraph的任務(wù),安排這些任務(wù)在某些線程上運行传惠。

GraphTask依賴網(wǎng)絡(luò)

重點代碼摘錄:

  • FGraphEvent
    class FGraphEvent
    {
     public:
         bool AddSubsequent(class FBaseGraphTask* Task)
         {
             return SubsequentList.PushIfNotClosed(Task);
         }
    
         /**
          *  Delay the firing of this event until the given event fires.
          *  CAUTION: This is only legal while executing the task associated with this event.
          *  @param EventToWaitFor event to wait for until we fire.
         **/
         void DontCompleteUntil(FGraphEventRef EventToWaitFor)
         {
             checkThreadGraph(!IsComplete()); // it is not legal to add a DontCompleteUntil after the event has been completed. Basically, this is only legal within a task function.
             new (EventsToWaitFor) FGraphEventRef(EventToWaitFor);
         }
    
         /**
          *  "Complete" the event. This grabs the list of subsequents and atomically closes it. Then for each subsequent it reduces the number of prerequisites outstanding and if that drops to zero, the task is queued.
          *  @param CurrentThreadIfKnown if the current thread is known, provide it here. Otherwise it will be determined via TLS if any task ends up being queued.
         **/
         CORE_API void DispatchSubsequents(TArray<FBaseGraphTask*>& NewTasks, ENamedThreads::Type CurrentThreadIfKnown = ENamedThreads::AnyThread);
    
         bool IsComplete() const
         {
             return SubsequentList.IsClosed();
         }
    
     private:
    
         /** Threadsafe list of subsequents for the event 依賴于該事件的任務(wù)列表 **/
         TClosableLockFreePointerListUnorderedSingleConsumer<FBaseGraphTask, 0>      SubsequentList;
         /** List of events to wait for until firing. This is not thread safe as it is only legal to fill it in within the context of an executing task. 附加的等待事件數(shù)組 **/
         FGraphEventArray                                                        EventsToWaitFor;
         /** Number of outstanding references to this graph event **/
         FThreadSafeCounter                                                      ReferenceCount;
     };
    
  • FBaseGraphTask
     class FBaseGraphTask
     {
     public:
         FBaseGraphTask(
             int32 InNumberOfPrerequistitesOutstanding
             )
             : ThreadToExecuteOn(ENamedThreads::AnyThread)
             , NumberOfPrerequistitesOutstanding(InNumberOfPrerequistitesOutstanding + 1) // + 1 is not a prerequisite, it is a lock to prevent it from executing while it is getting prerequisites, one it is safe to execute, call PrerequisitesComplete
         {
             checkThreadGraph(LifeStage.Increment() == int32(LS_Contructed));
         }
         
         void PrerequisitesComplete(ENamedThreads::Type CurrentThread, int32 NumAlreadyFinishedPrequistes, bool bUnlock = true)
         {
             checkThreadGraph(LifeStage.Increment() == int32(LS_PrequisitesSetup));
             int32 NumToSub = NumAlreadyFinishedPrequistes + (bUnlock ? 1 : 0); // the +1 is for the "lock" we set up in the constructor
             if (NumberOfPrerequistitesOutstanding.Subtract(NumToSub) == NumToSub) 
             {
                 QueueTask(CurrentThread);
             }
         }
         void ConditionalQueueTask(ENamedThreads::Type CurrentThread)
         {
             if (NumberOfPrerequistitesOutstanding.Decrement()==0)
             {
                 QueueTask(CurrentThread);
             }
         }
    
         // Subclass API
         FORCEINLINE void Execute(TArray<FBaseGraphTask*>& NewTasks, ENamedThreads::Type CurrentThread)
         {
             checkThreadGraph(LifeStage.Increment() == int32(LS_Executing));
             ExecuteTask(NewTasks, CurrentThread);
         }
         // Internal Use
         void QueueTask(ENamedThreads::Type CurrentThreadIfKnown)
         {
             checkThreadGraph(LifeStage.Increment() == int32(LS_Queued));
             FTaskGraphInterface::Get().QueueTask(this, ThreadToExecuteOn, CurrentThreadIfKnown);
         }
    
         /** Thread to execute on, can be ENamedThreads::AnyThread to execute on any unnamed thread **/
         ENamedThreads::Type         ThreadToExecuteOn;
         /** Number of prerequisites outstanding. When this drops to zero, the thread is queued for execution.  **/
         FThreadSafeCounter          NumberOfPrerequistitesOutstanding; 
     };
    
  • TGraphTask
      /** 
      *  TGraphTask
      *  Embeds a user defined task, as exemplified above, for doing the work and provides the functionality for setting up and handling prerequisites and subsequents
      **/
     template<typename TTask>
     class TGraphTask : public FBaseGraphTask
     {
     public:
         virtual void ExecuteTask(TArray<FBaseGraphTask*>& NewTasks, ENamedThreads::Type CurrentThread) final override
         {
             checkThreadGraph(TaskConstructed);
    
             // Fire and forget mode must not have subsequents
             // Track subsequents mode must have subsequents
             checkThreadGraph(XOR(TTask::GetSubsequentsMode() == ESubsequentsMode::FireAndForget, IsValidRef(Subsequents)));
    
             if (TTask::GetSubsequentsMode() == ESubsequentsMode::TrackSubsequents)
             {
                 Subsequents->CheckDontCompleteUntilIsEmpty(); // we can only add wait for tasks while executing the task
             }
    
             TTask& Task = *(TTask*)&TaskStorage;
             {
                 FScopeCycleCounter Scope(Task.GetStatId(), true); 
                 Task.DoTask(CurrentThread, Subsequents);
                 Task.~TTask();
                 checkThreadGraph(ENamedThreads::GetThreadIndex(CurrentThread) <= ENamedThreads::RenderThread || FMemStack::Get().IsEmpty()); // you must mark and pop memstacks if you use them in tasks! Named threads are excepted.
             }
    
             TaskConstructed = false;
    
             if (TTask::GetSubsequentsMode() == ESubsequentsMode::TrackSubsequents)
             {
                 FPlatformMisc::MemoryBarrier();
                 Subsequents->DispatchSubsequents(NewTasks, CurrentThread);
             }
    
             if (sizeof(TGraphTask) <= FBaseGraphTask::SMALL_TASK_SIZE)
             {
                 this->TGraphTask::~TGraphTask();
                 FBaseGraphTask::GetSmallTaskAllocator().Free(this);
             }
             else
             {
                 delete this;
             }
         }
         
         /** An aligned bit of storage to hold the embedded task **/
         TAlignedBytes<sizeof(TTask),ALIGNOF(TTask)> TaskStorage;
         /** Used to sanity check the state of the object **/
         bool                        TaskConstructed;
         /** A reference counted pointer to the completion event which lists the tasks that have me as a prerequisite. **/
         FGraphEventRef              Subsequents;  //該任務(wù)執(zhí)行完迄沫,需要激發(fā)該事件對象
    };
    

GraphTask與Event組成任務(wù)執(zhí)行依賴網(wǎng)絡(luò), 注意Event對象是可以動態(tài)綁定到一個GraphTask上的,但是根據(jù)設(shè)計需求同一時刻只能只能綁定到一個GraphTask上,
這個妙用請參閱

void FGraphEvent::DispatchSubsequents(TArray<FBaseGraphTask*>& NewTasks, ENamedThreads::Type CurrentThreadIfKnown = ENamedThreads::AnyThread)

的實現(xiàn)涉枫。
依賴網(wǎng)絡(luò)圖解:


TaskGraph_Library.jpg

任務(wù)的調(diào)度執(zhí)行

class FTaskGraphInterface負責(zé)對GraphTask的調(diào)度執(zhí)行邢滑,它根據(jù)GraphTask的要求安排到希望的線程上去執(zhí)行。細節(jié)請閱讀它的實現(xiàn)代碼愿汰。調(diào)試如下使用案例

  1. FRenderCommandFence類
     // Issue a fence command to the rendering thread and wait for it to complete.
     FRenderCommandFence Fence;
     Fence.BeginFence();
     Fence.Wait();
    
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末困后,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子衬廷,更是在濱河造成了極大的恐慌摇予,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,036評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件吗跋,死亡現(xiàn)場離奇詭異侧戴,居然都是意外死亡,警方通過查閱死者的電腦和手機跌宛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,046評論 3 395
  • 文/潘曉璐 我一進店門酗宋,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人疆拘,你說我怎么就攤上這事蜕猫。” “怎么了哎迄?”我有些...
    開封第一講書人閱讀 164,411評論 0 354
  • 文/不壞的土叔 我叫張陵回右,是天一觀的道長。 經(jīng)常有香客問我漱挚,道長翔烁,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,622評論 1 293
  • 正文 為了忘掉前任旨涝,我火速辦了婚禮蹬屹,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己哩治,他們只是感情好秃踩,可當(dāng)我...
    茶點故事閱讀 67,661評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著业筏,像睡著了一般。 火紅的嫁衣襯著肌膚如雪鸟赫。 梳的紋絲不亂的頭發(fā)上蒜胖,一...
    開封第一講書人閱讀 51,521評論 1 304
  • 那天,我揣著相機與錄音抛蚤,去河邊找鬼台谢。 笑死,一個胖子當(dāng)著我的面吹牛岁经,可吹牛的內(nèi)容都是我干的朋沮。 我是一名探鬼主播,決...
    沈念sama閱讀 40,288評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼缀壤,長吁一口氣:“原來是場噩夢啊……” “哼樊拓!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起塘慕,我...
    開封第一講書人閱讀 39,200評論 0 276
  • 序言:老撾萬榮一對情侶失蹤筋夏,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后图呢,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體条篷,經(jīng)...
    沈念sama閱讀 45,644評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,837評論 3 336
  • 正文 我和宋清朗相戀三年蛤织,在試婚紗的時候發(fā)現(xiàn)自己被綠了赴叹。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,953評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡指蚜,死狀恐怖乞巧,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情姚炕,我是刑警寧澤摊欠,帶...
    沈念sama閱讀 35,673評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站柱宦,受9級特大地震影響些椒,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜掸刊,卻給世界環(huán)境...
    茶點故事閱讀 41,281評論 3 329
  • 文/蒙蒙 一免糕、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦石窑、人聲如沸牌芋。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,889評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽躺屁。三九已至,卻和暖如春经宏,著一層夾襖步出監(jiān)牢的瞬間犀暑,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,011評論 1 269
  • 我被黑心中介騙來泰國打工烁兰, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留耐亏,地道東北人。 一個月前我還...
    沈念sama閱讀 48,119評論 3 370
  • 正文 我出身青樓沪斟,卻偏偏與公主長得像广辰,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子主之,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,901評論 2 355

推薦閱讀更多精彩內(nèi)容

  • 出入無人識择吊,進場撒滿地。 不肯與君見杀餐,只怕留下恨干发。
    我愛吃任何魚閱讀 153評論 0 2
  • 晚間跟隔壁的叔叔阿姨聊天,說講白話的故事史翘,說身體的病痛枉长,談關(guān)于年齡的趣事 …… 阿姨說她上學(xué)的時候...
    小社工閱讀 327評論 2 1
  • 【作者】陳天歌 【導(dǎo)師】劉艷 袁浩 鄭鵬 【導(dǎo)圖解說】這幅導(dǎo)圖,畫的是七年級下冊琼讽,第20課中的第一首古詩文必峰,《登幽...
    天藍的海閱讀 611評論 0 1