Hey guys, I made a small utility class, to run asynchronous functions that accept callbacks.
I use it for all sorts of things - loading assets, waiting until character arrives to some spot, waiting till camera finishes scrolling or waiting for player to close a modal pop-up.
This has nothing to do with Unreal’s queue system for doing heavy computations on different threads. It doesn’t do anything with threads at all, just wait for one function to finish before calling another one.
Think of it as latent blueprint nodes but for C++.
FAsyncQueue can be used to run asynchronous delegates in sequence, parallel and combinations of the above.
Use Add() to enqueue delegates matching FAsyncDelegate signature:
a void function that accepts a single argument of another void function with no arguments.
Static factories MakeSync, MakeSequence and MakeParallel can be used to wrap different type of delegates and
delegate collections into a single FAsyncDelegate which can be enqueued with Add().
Execute() accepts a callback and can be called multiple times. If queue is already running, Execute does nothing
except storing a callback.
The example bellow will output:
START
Starting Long Task ASYNC
//10 seconds later
Starting Short Task ASYNC
//1 second later
Doing Instant Task SYNC
Starting Longest Parallel ASYNC
Starting Shortest Parallel ASYNC
Starting Medium Parallel ASYNC
//1 second later
Finished Shortest Parallel ASYNC
//1 second later (2 seconds from parallel tasks started)
Finished Medium Parallel
//8 seconds later (10 seconds from parallel tasks started)
Finished Longest Parallel
DONESKIES
The example itself:
// Don't store the Queue on the stack or it will get destroyed before it finishes
// You can't use "new", only a factory method "FAsyncQueue::Create()" which always returns `TSharedRef<FAsyncQueue, ESPMode::ThreadSafe>`
Queue = FAsyncQueue::Create();
Queue->Add(FAsyncDelegate::CreateLambda([this](const FCallbackDelegate& Callback)
{
UE_LOG(LogTemp, Warning, TEXT("Starting Long Task ASYNC"));
FTimerHandle FooBar;
this->GetWorldTimerManager().SetTimer(FooBar, Callback, 10, false);
}));
Queue->Add(FAsyncDelegate::CreateLambda([this](const FCallbackDelegate& Callback)
{
UE_LOG(LogTemp, Warning, TEXT("Starting Short Task ASYNC"));
FTimerHandle FooBar;
this->GetWorldTimerManager().SetTimer(FooBar, Callback, 1, false);
}));
Queue->Add(FAsyncQueue::MakeSync(FCallbackDelegate::CreateLambda(]()
{
UE_LOG(LogTemp, Warning, TEXT("Doing Instant Task SYNC"));
})));
TArray<FAsyncDelegate> ParallelTasks;
TArray<FAsyncDelegate> LongestParallel;
LongestParallel.Add(FAsyncDelegate::CreateLambda([this](const FCallbackDelegate& Callback)
{
UE_LOG(LogTemp, Warning, TEXT("Starting Longest Parallel ASYNC"));
FTimerHandle FooBar;
this->GetWorldTimerManager().SetTimer(FooBar, Callback, 10, false);
}));
LongestParallel.Add(FAsyncQueue::MakeSync(FCallbackDelegate::CreateLambda(]()
{
UE_LOG(LogTemp, Warning, TEXT("Finished Longest Parallel"));
})));
ParallelTasks.Add(FAsyncQueue::MakeSequence(LongestParallel));
TArray<FAsyncDelegate> ShortestParallel;
ShortestParallel.Add(FAsyncDelegate::CreateLambda([this](const FCallbackDelegate& Callback)
{
UE_LOG(LogTemp, Warning, TEXT("Starting Shortest Parallel ASYNC"));
FTimerHandle FooBar;
this->GetWorldTimerManager().SetTimer(FooBar, Callback, 1, false);
}));
ShortestParallel.Add(FAsyncQueue::MakeSync(FCallbackDelegate::CreateLambda(]()
{
UE_LOG(LogTemp, Warning, TEXT("Finished Shortest Parallel"));
})));
ParallelTasks.Add(FAsyncQueue::MakeSequence(ShortestParallel));
TArray<FAsyncDelegate> MediumParallel;
MediumParallel.Add(FAsyncDelegate::CreateLambda([this](const FCallbackDelegate& Callback)
{
UE_LOG(LogTemp, Warning, TEXT("Starting Medium Parallel ASYNC"));
FTimerHandle FooBar;
this->GetWorldTimerManager().SetTimer(FooBar, Callback, 2, false);
}));
MediumParallel.Add(FAsyncQueue::MakeSync(FCallbackDelegate::CreateLambda(]()
{
UE_LOG(LogTemp, Warning, TEXT("Finished Medium Parallel"));
})));
ParallelTasks.Add(FAsyncQueue::MakeSequence(MediumParallel));
Queue->Add(FAsyncQueue::MakeParallel(ParallelTasks));
UE_LOG(LogTemp, Warning, TEXT("START"));
Queue->Execute(FCallbackDelegate::CreateLambda(]()
{
UE_LOG(LogTemp, Warning, TEXT("DONESKIES"));
}));
Here’s the source: Utility class for asynchronous/coroutine style programming in UE4 C++ · GitHub
Looking forward to your criticisism, suggestions and improvements.
Performance wise I have no idea how bad it is. I’ve made no attempt at optimisation, but it runs pretty well in my project.
It uses thread-safe delegates and pointers, but I haven’t tested it on multiple threads.