1. 程式人生 > 實用技巧 >初始多執行緒-2020-7-14

初始多執行緒-2020-7-14

Runnable執行緒和切換執行緒

首先模擬創造執行緒的過程,建立一個介面類,預留一個函式

// This class does not need to be modified.
UINTERFACE(MinimalAPI)
class UTaskInterface : public UInterface
{
    GENERATED_BODY()
};

/**
 * 
 */
class ADVANCEPROJECT_API ITaskInterface
{
    GENERATED_BODY()

    // Add interface functions to this class. This is the class that will be inherited to implement this interface.
public: virtual void DoWork(){} };

然後就是現執行緒類,繼承FRunnable

DECLARE_DELEGATE(FThreadDeclare)

class ADVANCEPROJECT_API ThreadTask : public FRunnable
{
public:
    ThreadTask();
    FThreadDeclare* MainThreadDeclare;

    virtual uint32 Run();

    void CreateThread(ITaskInterface* NewTaskInter);

    
~ThreadTask(); private: ITaskInterface* TaskInterface; FRunnableThread* Task_Thread; };

CPP:

ThreadTask::ThreadTask() { TaskInterface
=nullptr; }
//新執行緒開啟後,會執行重寫的Run方法 uint32 ThreadTask::Run() { UE_LOG(LogTemp,Log,TEXT(
"Run")); if (TaskInterface) {
     //新執行緒 TaskInterface
->DoWork();      //這裡插入一個執行緒方法,在他準備完畢後,會執行繫結的方法,會回到這個方法所規定執行緒,如果他繫結過的話。 FGraphEventRef taskRef = FFunctionGraphTask::CreateAndDispatchWhenReady([&]() { MainThreadDeclare->ExecuteIfBound(); },TStatId(),nullptr,ENamedThreads::GameThread); FTaskGraphInterface::Get().WaitUntilTaskCompletes(taskRef); } return 0; }
//真正意義上的開一個新執行緒
void ThreadTask::CreateThread(ITaskInterface* NewTaskInter) { TaskInterface = NewTaskInter; Task_Thread = FRunnableThread::Create(this,TEXT("HelloWorld"),0,TPri_Normal); } ThreadTask::~ThreadTask() { }

  NewInter = new ITaskInterface();
    
    NewTask = new ThreadTask();
    //繫結代理,將主執行緒的一個函式繫結給他的代理
    NewTask->MainThreadDeclare->BindUObject(this,&AAdvanceProjectGameModeBase::Print_V);
    //開闢執行緒,執行newtask的run()
    NewTask->CreateThread(NewInter);

圖例:

在create執行緒前,繫結工作還在主執行緒做

到run這裡,就變成自己的執行緒名了。

然後在切換執行緒部分,在執行函式準備完成之前,依舊還是在我們的執行緒裡工作。

準備完畢,進入代理函式,回到主執行緒

GraphTask執行緒

這四個介面方法為固定形式

#pragma once

#include "CoreMinimal.h"

class FTaskGraph
{
private:
    float _Str;
public:
    static ESubsequentsMode::Type GetSubsequentsMode()
    {
        return ESubsequentsMode::TrackSubsequents;
    }

    FORCEINLINE TStatId GetStatId()
    {
        RETURN_QUICK_DECLARE_CYCLE_STAT(FTaskGraph, STATGROUP_TaskGraphTasks);
    }

    void DoTask(ENamedThreads::Type CurrentThread, FGraphEventRef Subsequents)
    {
        UE_LOG(LogTemp, Log, TEXT("HelloWorld %f"),_Str);
    }

    static ENamedThreads::Type GetDesiredThread()
    {
        return ENamedThreads::AnyThread;
    }

    FTaskGraph(float NewStr): _Str(NewStr)
    {
    }

};

//支援構造方法傳遞引數
TGraphTask<FTaskGraph>::CreateTask(NULL, ENamedThreads::GameThread).ConstructAndDispatchWhenReady(7.7f);

AsyncTask執行緒

#pragma once

#include "CoreMinimal.h"

class FMyAsyncTask : public FNonAbandonableTask
{
    friend class FAsyncTask<FMyAsyncTask>;

    int32 InstanceInt;

public:
    FMyAsyncTask(int32 NewInt): InstanceInt(NewInt)
    {
    }

    void DoWork()
    {
        GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Red, TEXT("DoWork %d"));
        UE_LOG(LogTemp,Log,TEXT("DoWork %d"),InstanceInt);
    }

    FORCEINLINE TStatId GetStatId() const
    {
        RETURN_QUICK_DECLARE_CYCLE_STAT(FMyAsyncTask,STATGROUP_ThreadPoolAsyncTasks);
    }

    
};

   

   FAsyncTask<FMyAsyncTask>* MyAsync = new FAsyncTask<FMyAsyncTask>(666);
   //選擇是否為順序執行還是非同步執行,這裡是非同步 MyAsync
->StartBackgroundTask(); if (MyAsync->IsDone()) { delete MyAsync; MyAsync = nullptr; }

三種不同點

1.Runnable,是真正意義上的開新執行緒,開啟的過程比較消耗效能,這個執行緒可以用作大型複雜的計算,比如與資料庫、伺服器的互動

2.GraphTask執行緒可以確定自己的任務順序,其中一個引數可以放前置條件,只有前置條件完成後,才會執行本身

3.為了避免不必要的消耗,可以使用AsyncTask執行緒從執行緒池裡呼叫,直接使用。也不需要像GraphTask一樣建立一個函式類去初始化。

另外,只能在主執行緒完成的任務絕對不能放到其他執行緒執行。