New Core Feature: Async Framework (Master Branch and 4.8)

Sure!

Note: I’ve changed the names of many of my classes to conceal the purpose of this code, and I may have accidently chosen some that are already in use. They’re generic and stupid, so please pick better ones for your own code! Also, this example is a little too boilerplate - it’s how I did it, not how you /could/ do it in a general sense, so don’t feel the need to copy this approach directly (wrapper structs etc).


I have a struct that wraps my callback (it has some data that the worker uses, for now let’s assume that the in-data is a TArray of FStrings.



struct FAsyncDataRequest
{
	FAsyncDataRequest() {}

// callback is set manually, not by constructor (long story)
	FAsyncDataRequest(TArray<FString> a_meshIDs)
		: m_meshIDs(a_meshIDs) {}

	TArray<FString> m_meshIDs;
	FOnAsyncDataLoaded m_callback;
};


I pass this in to a worker that inherits from FNonAbandonableTask.



class FAsyncDataWorker : public FNonAbandonableTask
{
public:
	FOnAsyncDataLoaded OnLoadFinishedCallback; // This is the delegate called on the main thread when the data has been loaded.
		
public:
	FAsyncDataWorker ();
	~FAsyncDataWorker ();

// The owner of this worker pulled the data from the request and passed it in directly
	FAsyncDataWorker (TArray<FString> a_meshIDs, FString a_dataFilesDir); 

	void DoWork();
	TStatId GetStatId() const;

private:

// this is the function actually called by the taskgraph system to hop from the worker thread back to the main
// the data result struct just holds the results of this worker's DoWork (in my case, a TArray of structs).
	void OnLoadFinished(FAsyncDataResult a_dataLoadResult); 

	TArray<FString> m_meshIDs;
	FString m_dataFilesDir;
};

I also have a quick typedef, since you need to have an FAsyncTask template with your worker class that inherits from FNonAbandonableTask (it doesn’t have to inherit, but it’s easier using this stub I suppose).


typedef FAsyncTask<FAsyncDataWorker > FAsyncDataTask;

This is how the actual work begins:



// You can pass arguments into this constructor that are given to the FAsyncDataWorker's constructor
// since they're using a templated 'pass-through' constructor. Pretend that you're constructing the worker
// directly
FAsyncDataTask* asyncTask = new FAsyncDataTask(a_asyncDataRequest.m_meshIDs, m_baseDataPath);
// I bind the callback that will be executed on the main thread after load
asyncTask->GetTask().OnLoadFinishedCallback = a_asyncDataRequest.m_callback;
asyncTask->StartBackgroundTask();


This will start the FAsyncDataWorker::DoWork() function, which essentially does this:



void FAsyncDataWorker:: DoWork()
{

	FAsyncDataResult loadResult;
	MyDataLoaderClass dataLoader(m_dataFilesDir);
// the data loader returns my TArray of structs by reference
	dataLoader.LoadMyData(m_meshIDs, loadResult.TheTArrayOfStructs);

	FGraphEventRef ref = FSimpleDelegateGraphTask::CreateAndDispatchWhenReady
		(
			// works fine, but requires us to specify and create the delegate here, rather than
			// elsewhere and passing it in.
			FSimpleDelegateGraphTask::FDelegate::CreateRaw(this, &FAsyncDataWorker::OnLoadFinished, loadResult),
			TStatId(),
			nullptr,
			ENamedThreads::GameThread
		);
}


The delegate created above will be called back on the main thread, and will have the data load result as an argument. This ‘OnLoadFinished’ is essentially a function internal to our worker. All it does is the following:


void FAsyncDataWorker::OnLoadFinished(FAsyncDataResult a_loadResult)
{
		if (!OnLoadFinishedCallback.ExecuteIfBound(a_loadResult))
	{
		UE_LOG(LogTemp, Warning, TEXT("Failed to execute delegate when finished loading data in FAsyncDataWorker."));
	}
}

Now my original delegate is execute on the main thread, bound to a class about which this worker does not know, and I get the data I’ve loaded onto the main thread!


Remember, I had to change the names here due to it otherwise being obvious what I’m working on. Some of these names might be taken, so pick new ones intelligently :slight_smile: I may have screwed them up a little, too, so let me know.