AsyncTask:
Simplest way to start an Async task, that can run on either the game or any thread (see ENamedThreads).
No return value.
Executed on TaskGraph.
Used for small operations.
Manages threads by itself.
Does not ensure completion.
AsyncThread:
More advanced option, uses separate thread.
Thread priority can be set (see EThreadPriority).
TFuture return value possible.
Best for long running tasks (refers to using FRunnable).
Ideal for significant work-loads.
Does not ensure completion.
Async:
Flexible solution, though more limited than the others.
Executed on TaskGraph.
Does not ensure completion.
FAsyncTask:
Requires own body.
Comparable to AsyncTask, but basically a new class encabsulation a function.
Can be started and called from other parts of the code.
Does not ensure completion, though more reliable compared to the first three options.
Harder to set up in C++.
FNonAbandonableTask:
Requires own body.
Ideal for shorter tasks that can not be abandoned.
DOES ensure completion.
Harder to set up in C++.
FRunnable:
Requires own body.
Perfect for long running background tasks.
Also good for complex logic.
Multiple ones can be started to share workloads "easily".
Harder to set up in C++.
ParallelFor:
Broken as of UE 5.4 -> will stall the game!
Ideal for simple multithreading.
Simple to set-up.
Best for lots of data and Arrays.
FTask/TTask
A task that can simply run in the background.
TTask<T> can have a return value.
Issues storing the started task as a variable.
You can't easily check if the function has finished, though workaround can be made (not including Delegates).
In this example we'll write a static function that gets all Actors of Class, but asynchronously.
.h
DECLARE_DYNAMIC_DELEGATE(FDelegate_GetAllActorsOfClassAsync); // this is so that we can know, when the function has finished
// Class definition somehwere in here
/** Get All Actors of Class Asynchronously on GameThread.
* @param Finished Delegate for when the ASync function has finished.
* @param Class The Class to get all Actors from.
* @param Actors Input of an Array of Actors. Will be filled over time asynchronously. */
UFUNCTION(
BlueprintCallable,
meta=(UnsafeDuringActorConstruction)
static void GetAllActorsOfClassAsynchronously(
const TSubclassOf<AActor> Class,
UPARAM(ref) TArray<AActor*> &Actors,
FDelegate_GetAllActorsOfClassAsync Finished);
.cpp
// Get All Actors of Class Asynchronously on GameThread.
void UCPPFL_EUSU::GetAllActorsOfClassAsynchronously(
const TSubclassOf<AActor> Class,
TArray<AActor*> &Actors,
FDelegate_GetAllActorsOfClassAsync Finished)
{
// We need this in order to get the world we have to get the actors from
UWorld* World = GEngine->GameViewport->GetWorld();
AsyncTask(ENamedThreads::GameThread, [World, Class, &Actors, Finished]()
{
UGameplayStatics::GetAllActorsOfClass(World, Class, Actors);
Finished.Execute(); // calls the Delegate to let it know: hey, we done here
});
}
Important here is to use ENamedThreads::GameThread, because the function "GetAllActorsOfClass" forbidds any calling from anything other than the GameThread. If you do some simple things, though, other types can be used.
Hint: You can also give a "this" into the AsyncTask for a non-static function of for example an actor. This will enable the async task to have direct access to the objects data.
Now if used in Blueprints, make sure to crate a new custom event from "Finished" and then do something with it. Cause this lets us know, when the task has finished. Also, an Array of Actor must be presents, since this is where the task is writing its data to. Last but now leas: you don't have to use the "Finished" delegate and a custom event, but then it's not clear when the task will be finished. For example: you could put a delay of 1 seconds after the function and then access the Array and it might work. On a very slow machine, though and lots of Actors to go through, the list might miss a decent amount of entries.
The final result and an example on how to use it will look like this (incl. usage):
The ParallelFor() loop is the easiest and most powerful way to save on performance. In this example we'll start the loop and call a Delegate once it has finished.
.h
DECLARE_DYNAMIC_DELEGATE(FDelegate_ParallelForFinished);
// Class definition somehwere in here
UFUNCTION(BlueprintCallable)
static void CalculateStuff(
TArray<float> MyArray,
FDelegate_ParallelForFinished Finished);
.cpp
// Get All Actors of Class Asynchronously on GameThread.
void UClass::CalculateStuff(
TArray<float> MyArray,
FDelegate_ParallelForFinished Finished)
{
float CalculatedResult {};
ParallelFor(MyArray.Num(), [&CalculatedResult](int32 Index)
{
// Do Something with the Array and update "CalculatedResult"
}
Finished.Execute();
}
WIP!!!
So creating a Delegate that can return a TArray isn't quite as simple as it seems. It will create an error, but only when trying to use it within a blueprint. Something on the lines of "signature error"
// WON'T WORK: despite it being the most obvious, this won't work.
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FMyDelegate, TArray<AActor>, ActorArray);
// WILL WORK: makint the TArray a const with a Reference seems to work for whatever reason.
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FMyDelegate, const TArray<AActor>&, ActorArray);
With the second example you can output the Array to a delegate, for example for an Async Function.
Credits go to Sveitar from the Unreal Engine Forums.