UE C++如何取消异步任务?

· C++,UE技术分享,数字孪生

 

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消失之前,等待它完成吧。