UE4插件名:SimpleThread
版本:1.1
目录
- 前置环境测试准备
- 代理线程(FThreadProxyManage)
- 任务线程(FThreadTaskManagement)
- 同步异步线程(FThreadAbandonableManage)
- 协程(FCoroutinesManage)
- Windows原生线程(FWindowsPlatformThread)
- 异步资源读取(FResourceLoadingManage)
- 图表线程(ThreadGraphManage)
- 其他图表线程
- SimpleThread源码接口
哈喽,大家好,我叫人宅,这里我们为大家介绍一下关于UE4插件SimpleThread的使用技巧。
这是一款融合着多种线程模式的插件,SimpleThread,英译过来为简单的线程,并不是它的代码简单,而是使用者可以完全不用关心底层线程是如何运行,只管使用就好,只需要把事件绑定到该插件上即可完成各种多线程方案。
1.前置环境测试准备
我们先布置好要测试SimpleThread线程的环境
//锁
FCriticalSection Mutex;
//打印
void ThreadP(const FString Mes)
{
{
FScopeLock ScopeLock(&Mutex);
if (GEngine)
{
GEngine->AddOnScreenDebugMessage(-1, 100.f, FColor::Red, *Mes);
}
}
}
//结构体
struct FMyStruct
{
void Hello(FString Mes)
{
ThreadP(FString::Printf(TEXT("FMyStruct::Hello : %s"), *Mes));
}
};
//智能指针
struct FMyStructSP :public TSharedFromThis<FMyStructSP>
{
void HelloSP(FString Mes)
{
ThreadP(FString::Printf(TEXT("FMyStructSP::Hello : %s"), *Mes));
}
};
//类
UCLASS(config=Game)
class AThreadTestCharacter : public ACharacter
{
GENERATED_BODY()
public:
//测试 UObject
UFUNCTION()
void T1(int32 i);
//测试 UFunction
UFUNCTION()
void T2(int32 i, FString Mes);
UFUNCTION()
void Do();
UFUNCTION()
void Run();
UFUNCTION()
void OK();
protected:
virtual void BeginPlay();
};
void AThreadTestCharacter::BeginPlay()
{
Super::BeginPlay();
}
void AThreadTestCharacter::T1(int32 i)
{
ThreadP(FString::Printf(TEXT("T1 : %i"), i));
}
void AThreadTestCharacter::T2(int32 i, FString Mes)
{
ThreadP(FString::Printf(TEXT("T2 : %i ,Mes = %s"), i, *Mes));
}
void AThreadTestCharacter::OK()
{
ThreadP(TEXT("Windows Run"));
}
void AThreadTestCharacter::Run()
{
}
void AThreadTestCharacter::Do()
{
}
首先创建一个类AThreadTestCharacter 它是继承自 ACharacter。
然后我们来测试SimpleThread提供的各种线程。
2.代理线程(FThreadProxyManage)
代理线程有着自己的线程池方案,当你通过代理线程创建一个线程,那么这个线程会加入到线程池内,不会被销毁掉而是挂起,已等待下一个任务。
我们来演示一下它具体的使用方式:
CreateXXX:创建线程并且直接执行任务,是最快捷的异步方式
void AThreadTestCharacter::BeginPlay()
{
Super::BeginPlay();
FMyStruct MyStruct;
TSharedPtr<FMyStructSP> MyStructSP = MakeShareable(new FMyStructSP);
GThread::GetProxy().CreateUObject(this, &AThreadTestCharacter::T1, 777);
GThread::GetProxy().CreateRaw(&MyStruct, &FMyStruct::Hello, FString("Hello~"));
GThread::GetProxy().CreateSP(MyStructSP.ToSharedRef(), &FMyStructSP::HelloSP, FString("HelloSP~"));
GThread::GetProxy().CreateUFunction(this, TEXT("T2"), 123, "T22222");
GThread::GetProxy().CreateLambda([](FString Mes)
{
ThreadP(Mes);
}, "Lambda");
}
除此之外我们还有Bind
BindXXX:创建线程并且绑定任务,但不执行 通过 Join和Detach来决定是异步执行还是同步执行;
TArray<FThreadHandle> ThreadHandle;
void AThreadTestCharacter::BeginPlay()
{
Super::BeginPlay();
ThreadHandle.SetNum(5);
FMyStruct MyStruct;
TSharedPtr<FMyStructSP> MyStructSP = MakeShareable(new FMyStructSP);
ThreadHandle[0] = GThread::GetProxy().BindUObject(this, &AThreadTestCharacter::T1, 777);
ThreadHandle[1] = GThread::GetProxy().BindRaw(&MyStruct, &FMyStruct::Hello, FString("Hello~"));
ThreadHandle[2] = GThread::GetProxy().BindSP(MyStructSP.ToSharedRef(), &FMyStructSP::HelloSP, FString("HelloSP~"));
ThreadHandle[3] = GThread::GetProxy().BindUFunction(this, TEXT("T2"), 123, "T22222");
ThreadHandle[4] = GThread::GetProxy().BindLambda([](FString Mes)
{
ThreadP(Mes);
}, "Lambda");
GetWorld()->GetTimerManager().SetTimer(Handle, this, &AThreadTestCharacter::Do, 3.f);
}
//执行我们的线程与Std用法类似
void AThreadTestCharacter::Do()
{
if (Handle.IsValid())
{
GetWorld()->GetTimerManager().ClearTimer(Handle);
}
//同步执行
//for (auto &Tmp : ThreadHandle)
//{
// GThread::GetProxy().Join(Tmp);
//}
//异步执行
//for (auto &Tmp : ThreadHandle)
//{
// GThread::GetProxy().Detach(Tmp);
//}
}
3.任务线程(FThreadTaskManagement)
使用任务线程,可以往线程里面不断的去放任务
BindXXX 添加到任务队列中 如果有空置的线程可以直接执行该任务;
我们来看看它的实际运用:
void AThreadTestCharacter::BeginPlay()
{
Super::BeginPlay();
GThread::GetTask().BindUObject(this, &AThreadTestCharacter::T1, 777);
GThread::GetTask().BindRaw(&MyStruct1, &FMyStruct::Hello, FString("Hello~"));
GThread::GetTask().BindSP(MyStructSP1.ToSharedRef(), &FMyStructSP::HelloSP, FString("HelloSP~"));
GThread::GetTask().BindUFunction(this, TEXT("T2"), 123, FString("T22222"));
GThread::GetTask().BindLambda([](FString Mes)
{
ThreadP(Mes);
}, "Lambda");
}
用法和代理线程差不多,我们来看看CreateXXX
CreateXXX 直接在线程池里面找 如果有闲置的线程 直接运行当前任务;
void AThreadTestCharacter::BeginPlay()
{
Super::BeginPlay();
GThread::GetTask().CreateUObject(this, &AThreadTestCharacter::T1, 777);
GThread::GetTask().CreateRaw(&MyStruct1, &FMyStruct::Hello, FString("Hello~"));
GThread::GetTask().CreateSP(MyStructSP1.ToSharedRef(), &FMyStructSP::HelloSP, FString("HelloSP~"));
GThread::GetTask().CreateUFunction(this, TEXT("T2"), 123, FString("T22222"));
GThread::GetTask().CreateLambda([](FString Mes)
{
ThreadP(Mes);
}, "Lambda");
}
根据官方:任务线程的以后会添加优先级任务设置的扩展;
4.同步异步线程(FThreadAbandonableManage)
该线程是UE4线程池内的线程,轻巧便捷,要么执行同步,要么执行异步,同样我们的同步异步线程也有Bind和Create
BindXXX 同步绑定 会阻塞启动线程 完成任务后激活启动线程
void AThreadTestCharacter::BeginPlay()
{
Super::BeginPlay();
GThread::GetAbandonable().BindUObject(this, &AThreadTestCharacter::T1, 777);
GThread::GetAbandonable().BindRaw(&MyStruct1, &FMyStruct::Hello, FString("Hello~"));
GThread::GetAbandonable().BindSP(MyStructSP1.ToSharedRef(), &FMyStructSP::HelloSP, FString("HelloSP~"));
GThread::GetAbandonable().BindUFunction(this, TEXT("T2"), 123, FString("T22222"));
GThread::GetAbandonable().BindLambda([](FString Mes)
{
ThreadP(Mes);
}, "Lambda");
}
CreateXXX 异步绑定 直接启动,任务完成后自动销毁;
void AThreadTestCharacter::BeginPlay()
{
Super::BeginPlay();
GThread::GetAbandonable().CreateUObject(this, &AThreadTestCharacter::T1, 777);
GThread::GetAbandonable().CreateRaw(&MyStruct1, &FMyStruct::Hello, FString("Hello~"));
GThread::GetAbandonable().CreateSP(MyStructSP1.ToSharedRef(), &FMyStructSP::HelloSP, FString("HelloSP~"));
GThread::GetAbandonable().CreateUFunction(this, TEXT("T2"), 123, FString("T22222"));
GThread::GetAbandonable().CreateLambda([](FString Mes)
{
ThreadP(Mes);
}, "Lambda");
}
除此之外还添加了便捷的宏启动线程,该线程也是可以执行同步异步
同步宏
void AThreadTestCharacter::BeginPlay()
{
Super::BeginPlay();
SYNCTASK_UOBJECT(this, &AThreadTestCharacter::T1, 777);
SYNCTASK_Raw(&MyStruct1, &FMyStruct::Hello, FString("Hello~"));
SYNCTASK_SP(MyStructSP1.ToSharedRef(), &FMyStructSP::HelloSP, FString("HelloSP~"));
SYNCTASK_UFunction(this, TEXT("T2"), 123, FString("T22222"));
SYNCTASK_Lambda([](FString Mes)
{
ThreadP(Mes);
}, "Lambda");
}
异步宏
void AThreadTestCharacter::BeginPlay()
{
Super::BeginPlay();
ASYNCTASK_UOBJECT(this, &AThreadTestCharacter::T1, 777);
ASYNCTASK_Raw(&MyStruct1, &FMyStruct::Hello, FString("Hello~"));
ASYNCTASK_SP(MyStructSP1.ToSharedRef(), &FMyStructSP::HelloSP, FString("HelloSP~"));
ASYNCTASK_UFunction(this, TEXT("T2"), 123, FString("T22222"));
ASYNCTASK_Lambda([](FString Mes)
{
ThreadP(Mes);
}, "Lambda");
}
5.协程(FCoroutinesManage)
该插件也加入了协程功能,同样也有Bind和Create的区别;
Bind XXX 绑定后可以设置时间,多久执行
void AThreadTestCharacter::BeginPlay()
{
Super::BeginPlay();
//设置1s后执行函数T1
GThread::GetCoroutines().BindUObject(1.f, this, &AThreadTestCharacter::T1, 777);
GThread::GetCoroutines().BindRaw(2.f, &MyStruct1, &FMyStruct::Hello, FString("Hello~"));
GThread::GetCoroutines().BindSP(2.4f, MyStructSP1.ToSharedRef(), &FMyStructSP::HelloSP, FString("HelloSP~"));
GThread::GetCoroutines().BindUFunction(4.f, this, TEXT("T2"), 123, FString("T22222"));
GThread::GetCoroutines().BindLambda(7.f, [](FString Mes)
{
ThreadP(Mes);
}, "Lambda");
}
CreateXXX 绑定完毕返回一个Handle ,由程序员来决定什么时候执行;
TArray<FCoroutinesHandle> CoroutinesHandle;
void AThreadTestCharacter::BeginPlay()
{
Super::BeginPlay();
CoroutinesHandle.SetNum(5);
CoroutinesHandle[0] = GThread::GetCoroutines().CreateUObject(this, &AThreadTestCharacter::T1, 777);
CoroutinesHandle[1] = GThread::GetCoroutines().CreateRaw(&MyStruct1, &FMyStruct::Hello, FString("Hello~"));
CoroutinesHandle[2] = GThread::GetCoroutines().CreateSP(MyStructSP1.ToSharedRef(), &FMyStructSP::HelloSP, FString("HelloSP~"));
CoroutinesHandle[3] = GThread::GetCoroutines().CreateUFunction(this, TEXT("T2"), 123, FString("T22222"));
CoroutinesHandle[4] = GThread::GetCoroutines().CreateLambda([](FString Mes)
{
ThreadP(Mes);
}, "Lambda");
GetWorld()->GetTimerManager().SetTimer(Handle, this, &AThreadTestCharacter::Do, 3.f);
}
//执行
void AThreadTestCharacter::Do()
{
if (Handle.IsValid())
{
GetWorld()->GetTimerManager().ClearTimer(Handle);
}
for (auto &Tmp : CoroutinesHandle)
{
if (Tmp.IsValid())
{
//唤醒该协程下的事件
Tmp.Pin()->Awaken();
}
}
}
6.Windows原生线程(FWindowsPlatformThread)
void AThreadTestCharacter::BeginPlay()
{
Super::BeginPlay();
FWindowsPlatformThread::RunDelegate.BindUObject(this, &AThreadTestCharacter::Run);
FWindowsPlatformThread::CompletedDelegate.BindUObject(this, &AThreadTestCharacter::OK);
FWindowsPlatformThread::Show();//执行线程
}
//正在执行
void AThreadTestCharacter::Run()
{
...
}
//执行完成
void AThreadTestCharacter::OK()
{
ThreadP(TEXT("Windows Run"));
}
7.异步资源读取(FResourceLoadingManage)
异步资源读取我们分为同步读取和异步读取,我们来看看它的使用方法
异步
UCLASS(config=Game)
class AThreadTestCharacter : public ACharacter
{
GENERATED_BODY()
public:
//从蓝图加载资源路径
UPROPERTY(EditDefaultsOnly, meta = (AllowPrivateAccess = "true"))
TArray<FSoftObjectPath> ObjectPath;
}
void AThreadTestCharacter::BeginPlay()
{
Super::BeginPlay();
auto La = [](TSharedPtr<FStreamableHandle> *InHandle)
{
TArray<UObject *> ExampleObject;
(*InHandle)->GetLoadedAssets(ExampleObject);
for (UObject *Tmp : ExampleObject)
{
ThreadP(Tmp->GetName());
}
};
//异步使用方法
GThread::GetResourceLoading() >> ObjectPath;
StreamableHandle = GThread::GetResourceLoading().CreateLambda(La, &StreamableHandle);
}
同步
UCLASS(config=Game)
class AThreadTestCharacter : public ACharacter
{
GENERATED_BODY()
public:
//从蓝图加载资源路径
UPROPERTY(EditDefaultsOnly, meta = (AllowPrivateAccess = "true"))
TArray<FSoftObjectPath> ObjectPath;
}
void AThreadTestCharacter::BeginPlay()
{
Super::BeginPlay();
auto La = [](TSharedPtr<FStreamableHandle> *InHandle)
{
TArray<UObject *> ExampleObject;
(*InHandle)->GetLoadedAssets(ExampleObject);
for (UObject *Tmp : ExampleObject)
{
ThreadP(Tmp->GetName());
}
};
//同步
//////////////////////////////////////////////////////////////////////////
StreamableHandle = GThread::GetResourceLoading() << ObjectPath;
La(&StreamableHandle);
}
8.图表线程(ThreadGraphManage)
图表线程是UE4使用频率最高的线程,它可以实现线程的前置任务,备受UE4线程喜爱,我们来演示一下该线程的使用技巧.
同样该线程也是包含Bind和Create
BindXXX :只呼叫主线程
TArray<FGraphEventRef > ArrayEventRef;
void AThreadTestCharacter::BeginPlay()
{
Super::BeginPlay();
ArrayEventRef.SetNum(5);
ArrayEventRef[0] = GThread::GetGraph().BindUObject(this, &AThreadTestCharacter::T1, 777);
ArrayEventRef[1] = GThread::GetGraph().BindRaw(&MyStruct1, &FMyStruct::Hello, FString("Hello~"));
ArrayEventRef[2] = GThread::GetGraph().BindSP(MyStructSP1.ToSharedRef(), &FMyStructSP::HelloSP, FString("HelloSP~"));
ArrayEventRef[3] = GThread::GetGraph().BindUFunction(this, TEXT("T2"), 123, FString("T22222"));
ArrayEventRef[4] = GThread::GetGraph().BindLambda([](FString Mes)
{
ThreadP(Mes);
}, "Lambda");
//GThread::GetGraph().Wait(ArrayEventRef[0]);
FGraphEventArray ArrayEvent;
for (auto &Tmp : ArrayEventRef)
{
ArrayEvent.Add(Tmp);
}
//可以设置等待这些线程完成任务后再执行自己
GThread::GetGraph().Wait(ArrayEvent);
ThreadP("Wait-oK");
}
CreateXXX 绑定任意线程
void AThreadTestCharacter::BeginPlay()
{
Super::BeginPlay();
GThread::GetGraph().CreateUObject(this, &AThreadTestCharacter::T1, 777);
GThread::GetGraph().CreateRaw(&MyStruct1, &FMyStruct::Hello, FString("Hello~"));
GThread::GetGraph().CreateSP(MyStructSP1.ToSharedRef(), &FMyStructSP::HelloSP, FString("HelloSP~"));
GThread::GetGraph().CreateUFunction(this, TEXT("T2"), 123, FString("T22222"));
GThread::GetGraph().CreateLambda([](FString Mes)
{
ThreadP(Mes);
}, "Lambda");
}
同样它也可以返回事件引用,方便其他图表线程等待该任务
9.其他图表线程
除此之外还有宏类型的图表线程,它的使用更加强大,可以对单个事件进行等待,我们来演示一下
TArray<FGraphEventRef > ArrayEventRef;
void AThreadTestCharacter::BeginPlay()
{
Super::BeginPlay();
auto A = CALL_THREAD_UOBJECT(NULL, ENamedThreads::AnyThread, this, &AThreadTestCharacter::T1, 777);
//等待A事件完成执行B事件
auto B = CALL_THREAD_Raw(A, ENamedThreads::AnyThread, &MyStruct1, &FMyStruct::Hello, FString("Hello~"));
auto C = CALL_THREAD_SP(NULL, ENamedThreads::AnyThread, MyStructSP1.ToSharedRef(), &FMyStructSP::HelloSP, FString("HelloSP~"));
auto D = CALL_THREAD_UFunction(NULL, ENamedThreads::AnyThread, this, TEXT("T2"), 123, FString("T22222"));
auto E = CALL_THREAD_Lambda(, NULL, ENamedThreads::AnyThread, [](FString Mes)
{
ThreadP(Mes);
}, "Lambda");
}
10.SimpleThread源码接口
#pragma once
#include "CoreMinimal.h"
#include "Core/Manage/ThreadProxyManage.h"
#include "Core/Manage/ThreadTaskManage.h"
#include "Core/Manage/ThreadAbandonableManage.h"
#include "Core/Manage/CoroutinesManage.h"
#include "Core/Manage/ResourceLoadingManage.h"
#include "Tickable.h"
namespace TM
{
//FThreadManagement 是线程安全的,不容易出现死锁
class SIMPLETHREAD_API FThreadManagement : public TSharedFromThis<FThreadManagement>, public FTickableGameObject
{
public:
static TSharedRef<FThreadManagement> Get();
static void Destroy();
//~Tick
private:
virtual void Tick(float DeltaTime);
virtual TStatId GetStatId() const;
public:
static FThreadProxyManage &GetProxy() { return Get()->ThreadProxyManage; }
static FThreadTaskManagement &GetTask() { return Get()->ThreadTaskManagement; }
static FThreadAbandonableManage &GetAbandonable() { return Get()->ThreadAbandonableManage; }
static FCoroutinesManage &GetCoroutines() { return Get()->CoroutinesManage; }
static FResourceLoadingManage &GetResourceLoading() { return Get()->ResourceLoadingManage; }
protected:
//自定义线程创建,可以简单直接的创建线程
FThreadProxyManage ThreadProxyManage;
//自定义的线程池,可以往线程池内丢任务,令其执行
FThreadTaskManagement ThreadTaskManagement;
//从UE4线程池内直接取线程执行
FThreadAbandonableManage ThreadAbandonableManage;
//协程
FCoroutinesManage CoroutinesManage;
//资源读取
FResourceLoadingManage ResourceLoadingManage;
private:
static TSharedPtr<FThreadManagement> ThreadManagement;
};
}
using namespace TM;
typedef TM::FThreadManagement GThread;
void Example()
{
TArray<FSoftObjectPath> ObjectPath;
TSharedPtr<FStreamableHandle> Handle;
auto La = [&Handle]()
{
TArray<UObject *> ExampleObject;
Handle->GetLoadedAssets(ExampleObject);
};
//异步使用方法
Handle = GThread::GetResourceLoading() >> ObjectPath >> FSimpleDelegate::CreateLambda(La);
//同步
//////////////////////////////////////////////////////////////////////////
Handle = GThread::GetResourceLoading() << ObjectPath;
La();
}
好,以上是UE4SimpleThread插件的详细教程,希望该教程能对您有帮助
更新日志
2021年12月28号:修复多线程5.0中的协程中只能执行一个协程函数的bug
2022年5月14号:协程注册,有几率导致断言,该bug已经修复。
如果在使用本插件有什么问题可以问我
4.27版本没有吗,用4.26的无法生效
已经在处理了
linux 可以使用吗?
我购买了这个教程,有学习群吗?
有学习群 建议购买教程,这个插件找我开通
我已经再aboutcg购买了教程了,怎么联系你开通插件呢?
随便加一个群,然后私聊我。比如咨询群。
我已经再aboutcg购买了教程了,怎么联系你开通插件呢?(代码刚学习完,但是第七章测试自己的代码时就有问题了)
随便加一个群,然后私聊我。
Hello Renzhai, my name is Idir Belaid, i’m learning UE5, i am interested in buying your content,
but i’m having difficulty trying to pay, how can i contact you ?
this is my email: idir.belaid.83@gmail.com
Currently, only Alipay is supported, and other payments have not been connected。
alright, thanks, sorry for late reply, can you help me create an account on http://www.aboutcg.org, i don’t understand how to register, i wanna buy your multi-threading and UE5 courses, email me if you have questions, thank you in advance.
VPN is required to access AboutCG,Unable to access overseas.
i can get VPN (Nord VPN) but i don’t have Chinese phone number to open account, can you help ? i need to learn multi-threading and task graph, for planet generator and other procedural stuff, if i manage to ship games, ill credit you and compensate you from the sale for your help, i am a fair gentleman.
https://www.youtube.com/watch?v=edV4fbmuDCQ
I don’t know how to help you. Try a virtual phone, which may be a good idea.