UE提供了 FAsyncTask 以及 FAutoDeleteAsyncTask来实现异步任务的运行,其中FAutoDeleteAsyncTask 任务运行完成后,会自动的销毁FAutoDeleteAsyncTask对象,使用起来也很简单方便。
可以看UE在AsyncWork.h文件里面提供的Sample代码:
classExampleAutoDeleteAsyncTask : public FNonAbandonableTask
{
friend classFAutoDeleteAsyncTask;
int32 ExampleData;
ExampleAutoDeleteAsyncTask(int32InExampleData)
:ExampleData(InExampleData)
{
}
void DoWork()
{
... do the work here
}
FORCEINLINE TStatId GetStatId() const
{
RETURN_QUICK_DECLARE_CYCLE_STAT(ExampleAutoDeleteAsyncTask,STATGROUP_ThreadPoolAsyncTasks);
}
};
void Example()
{
// start an example job
(newFAutoDeleteAsyncTask(5)->StartBackgroundTask();
// do an example job now, on this thread
(newFAutoDeleteAsyncTask(5)->StartSynchronousTask();
}
在开发过程中,我希望实现流关卡的后台异步加载,就写了一个类FLoadStreamLevelAsyncTask来实现流关卡的加载,由于FNonAbandonableTask只是定义了CanAbandon和 Abandon两个虚函数,所以我就直接写了两个虚函数,而没有从这个类派生了,效果是一样的。
class FLoadStreamLevelAsyncTask
{
FString TaskName;
FString LevelName;
UDCRLevelManager* LevelManager;
UWorld* WorldToAdd;
FTransform Transform;
friend class FAutoDeleteAsyncTask<FLoadStreamLevelAsyncTask>;
friend class FAsyncTask<FLoadStreamLevelAsyncTask>;
public:
// 构造函数
FLoadStreamLevelAsyncTask(UWorld* InWorld, UDCRLevelManager* InLevelManager, const FString& InLevelName, const FTransform& InTransform);
// 析构函数
virtual ~FLoadStreamLevelAsyncTask();
virtual bool CanAbandon();
virtual void Abandon();
// 具体任务逻辑执行
void DoWork();
// 固定写法,本类将作为函数参数
FORCEINLINE TStatId GetStatId() const
{
RETURN_QUICK_DECLARE_CYCLE_STAT(FLoadStreamLevelAsyncTask, STATGROUP_ThreadPoolAsyncTasks);
}
};
在加载流关卡时候使用如下两行代码,就加载了子关卡,非常简便易用。
auto Task = newFAutoDeleteAsyncTask(InWorld, this, level.Key,level.Value);
Task->StartBackgroundTask();
随即问题来了,加载关卡有时候是个比较长周期的任务,同时关卡可能比较多,某些关卡可能还没有加载进来,就已经不需要了(比如切换到别的场景去了),甚至程序已经退出了,这时候异步任务还留在线程池里,但执行异步任务的环境,如world对象已经不存在了,这种情况下就会导致程序的崩溃,那如何控制和取消异步任务呢?
使用FAutoDeleteAsyncTask这时候就不合适了,所以我就改成用FAsyncTask来执行异步任务。
首先,使用智能指针数组来保存所有的异步任务:
typedef FAsyncTask<FLoadStreamLevelAsyncTask> AsyncLoadTask;
TArray<TSharedPtr<AsyncLoadTask>> LoadLevelTasks;
TSharedPtr<AsyncLoadTask> TaskElem
= MakeShareable(new AsyncLoadTask(InWorld, this, level.Key, level.Value));
LoadLevelTasks.Add(TaskElem);
TaskElem->StartBackgroundTask();
其次,启动timer对象,定时检查任务是否已经完成:
InWorld->GetTimerManager().SetTimer(CheckTimer, this,&UDCRLevelManager::CheckLoadProcess, 1.5f, true, 5.0f);
在CheckLoadProcess函数里检查任务是否完成,并把已经完成的任务从数组中删除。
void UDCRLevelManager::CheckLoadProcess()
{
for (int32 index=0;index< LoadLevelTasks.Num(); index++)
{
TSharedPtr<AsyncLoadTask> Task = LoadLevelTasks[index];
if (Task->IsDone())
{
LoadLevelTasks.RemoveAt(index);
index--;
}
}
//如果所有任务都已经完成,则删除Timer
if (GWorld && CheckTimer.IsValid()&& LoadLevelTasks.Num()==0)
{
GWorld->GetTimerManager().ClearTimer(this->CheckTimer);
CheckTimer.Invalidate();
}
};
同时,实现一个取消任务的方法:
void UDCRLevelManager::AbandonTasks()
{
//先停掉timer
if (GWorld&& CheckTimer.IsValid())
{
GWorld->GetTimerManager().ClearTimer(this->CheckTimer);
CheckTimer.Invalidate();
}
for (int32 index = 0; index < LoadLevelTasks.Num();index++)
{
TSharedPtr<AsyncLoadTask> Task = LoadLevelTasks[index];
if (Task->IsDone())
{
//已经完成
LoadLevelTasks.RemoveAt(index);
index--;
}
else if(Task->Cancel())
{
//能够取消
LoadLevelTasks.RemoveAt(index);
index--;
}
else
{
//已经在执行,那就等执行完吧
Task->EnsureCompletion(false);
}
}
LoadLevelTasks.Empty();
}
这里的关键是有些异步任务在执行,这时候调用Cancel()他是无法取消了的,只能是EnsureCompletion等待他完成,确保在world消失之前,等待它完成吧。