赞
踩
转载:原文地址
多线程在任何语言中都是不可或缺的,意义重大的,因此,能熟练使用多线程往往是一件能让工作事半功倍的事情。那么做为强大的UE4引擎,又给了我们什么支持呢?很多人可能知道runable,知道tick但是这都只是UE4多线程的一部分,我开始也就知道这些,然后用起来总觉得哪里不对劲,经过一段时间,终于找到了适合自己的方法,不得不感慨下,UE4的东西真的是太多了,还是需要多多研究,然后再这里分享下我知道的一些好用的方法。
这个应该是UE4最常见的方法了,我们都知道在UE4里面,最基础的类是Uobject,而继承自Uobject的AActor,,就有这个tick方法了,而地图中的大部分对象,都是继承自AActor的,除了AActor,component和UMG也都有对应的tick机制。而这也就意味着,大多数的时候,我们只需要重载基类的tick就可以使用tick了。
有时候我们可能需要好几个tick,或者我们不想继承自aacotor以及一些自带tick的类,这个时候我们就需要使用timer了,timer使用起来怎么说呢,不是那么好用吧,因为UE4是以世界为驱动的,所以我们的timer并不能离开世界,这点是我不太能接受的,但是不管怎么说,timer还是有的,只是不像其语言用起来那么方便,而且即使不方便也就是一点点。
FTimerHandle m_hTimerHandle;
GetWord()->GetTimerManager().SetTimer(
m_hTimerHandle,//timer的句柄
this,//处理事件的对象
&UNetPlayManager::TimerTick,//处理事件的方法
1.0,//触发间隔时间
true//循环触发
);
GetWorld()->GetTimerManager().ClearTimer(m_hTimerHandle);
这个方法主要用来把一些任务转交给主线程处理。当然你也可以选择交给任何其他有名或者无名的线程。
AsyncTask(ENamedThreads::GameThread, [=]()
{
//….一些操作
});
使用起来就这么简单,这里需要说明的是,因为这个任务是丢给别的线程处理,实际上不是用来处理多线程的事件的,至少,它不具备处理循环的能力,你这里如果丢一个无限循环进去,那不光是你自己的目的达不到,还会卡住了其他线程。所以这里实际算是一个异步的调用,这句话的实际意思就是,我这里有个任务,我自己不做,给别人做了,但是这个事只能是一次性的,而且,你给人家做,人家还指不定什么时候做,做完了也不会通知你,所以这里相当于一个异步的调用。
Async和 AsyncTask有比较少的区别,主要在第一个参数上面:
TaskGraph是将其放到任务图中去执行
Thread则是在单独的线程中执行
TreadPool则是放入线程池中去执行。
而AsyncTask的第一个参数则是将任务放在指定的线程执行。
FAutoDeleteAsyncTask是一个模板,使用友元的特性,让模板类可以操作类的私有函数,算是学到了,不错的用法,有兴趣的可以自己研究下。要使用FAutoDeleteAsyncTask,需要实现几个函数:
class ExampleAsyncTask : public FNonAbandonableTask { friend class FAutoDeleteAsyncTask<ExampleAsyncTask>; int32 ExampleData; ExampleAsyncTask(int32 InExampleData) : ExampleData(InExampleData) { } void DoWork() { ... do the work here } FORCEINLINE TStatId GetStatId() const { RETURN_QUICK_DECLARE_CYCLE_STAT(ExampleAsyncTask, STATGROUP_ThreadPoolAsyncTasks); } };
定义好之后有两种方式启动线程,区别主要是任务在哪个线程执行。如果是丢到线程池的话,可以自己指定线程池,如果不指定,就在一个全局的线程池QueuedPool中
// 将任务扔到线程池中去执行
(new FAutoDeleteAsyncTask<ExampleAsyncTask>(5))->StartBackgroundTask();
// 直接在当前线程执行操作
(new FAutoDeleteAsyncTask<ExampleAsyncTask>(5))->StartSynchronousTask();
FAutoDeleteAsyncTask的优点在于任务执行完之后,任务会被自动删除掉,不需要任何其他操作。下面是FAutoDeleteAsyncTask的dowork函数,可以看到,task的任务执行完成之后,函数就会自己清除自己。
void DoWork()
{
LLM_SCOPE(InheritedLLMTag);
FScopeCycleCounter Scope(Task.GetStatId(), true);
Task.DoWork();
delete this;
}
和上面的FAutoDeleteAsyncTask实现一样,区别在于,不进行后台运行任务的时候,需要自己进行一些处理,保证任务完成之后自己删除任务。
MyTask->StartSynchronousTask();
//to just do it now on this thread
//Check if the task is done :
if (MyTask->IsDone())
{
}
//Spinning on IsDone is not acceptable( see EnsureCompletion ), but it is ok to check once a frame.
//Ensure the task is done, doing the task on the current thread if it has not been started, waiting until completion in all cases.
MyTask->EnsureCompletion();
delete Task;
这个类也是我比较喜欢的开启多线程的类,直接继承FRunnable,然后重载三个函数:
Init() 初始化线程的一些参数
Run() 任务主体
Exit() 线程结束清理
执行顺序是:先执行Init,如果失败就不会执行Run(),Run()执行完成就会执行Exit()。
然后在使用的时候不能直接使用,需要使用FRunnableThread::create来启动程序。
这里有两种用法:
FRunnableThread::create(this,TEXT("name");
FRunnable* runnable = new FRunnableTest();
FRunnableThread::create(runnable,TEXT("name");
不管哪一种用法,最好都要把create返回的句柄保存一下,在线程对象清除的时候也要清除这个句柄。
这个就是开篇提到的,依赖最小的线程。一直以来我都喜欢职责单一单一原则,和解耦合,所以喜欢用些尽量独立的方法,让方法模块自己本身也更独立。如果同时又能很方便使用,那就最好了。
说起FTickableGameObject最大的好处,应该就是不需要是原生的UE4的类就可以使用了,本来它自己也是F开头的类,也就是普通类。想要使用它,只需要直接继承这个类然后实现几个函数就可以了。
public:
/** <Tick接口函数 */
virtual void Tick(float DeltaTime) override;
virtual bool IsTickable() const override
{
return true;
}
virtual bool IsTickableWhenPaused() const override
{
return true;
}
virtual TStatId GetStatId() const override
{
RETURN_QUICK_DECLARE_CYCLE_STAT(threadname, STATGROUP_Tickables);
}
因为Tick 和GetStatId是纯虚函数,所以这里必须要实现,另外两个可以看到只是做下设置。
重载的时候tick就不需要多说了,GetStatId里面只要修改一个独一无二的字符串做id就可以了。threadname不需要用双引号写成字符串的形式,这里是一个宏的参数,你写成什么样子,最后就叫啥。
这个需要和FScopeLock 配合使用。
FCriticalSection CriticalSection;
FScopeLock Lock(&CriticalSection);
暂时写这么多吧,我也是刚开始研究,有什么不对的地方,欢迎指出,大家一起学习,一起进步。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。