Thread safety, delegates and bPostTickComponentUpdate

So I’m using a hardware input device that runs its own thread (inside its own dll, that I have no source for unfortunately) and calls specific functions on a listener class I define. That listener class then in turn broadcasts the events through delegates to instances of an Actor Component, which themselves have delegates that can be listened to in Blueprint.
However, when I actually run tests with this, I get assert failures in void

UWorld::MarkActorComponentForNeededEndOfFrameUpdate,

specifically
check(!bPostTickComponentUpdate) on line 705 in LevelTick.cpp.

I’m going to go out on a limb here and say that it looks like I’m running into issues where the thread that the library is creating isn’t interacting with my delegates and Blueprint in a thread-safe way, or at the least is broadcasting the delegate at the wrong points in the main loop. I tried to put a check for GetWorld()->bPostTickComponentUpdate into my code, just as a test, to see if this would help me synch my delegate
invocations at the right point in time, but it didn’t really seem to help.

Is there any information regarding thread safety with delegates/blueprint/actor ticking that I can look at, to avoid this?

Actors can be only ticked on the game thread. I suggest using task graph task that will be executed on the game thread, then you are safe to call any delegates.

Here is a code snippet. You can use simple delegate task or write your own task.

struct FParameters
{
	uint8* Data;
};

// Task graph.
void SomeFunctionThatWillBeCalledOnGameThread( FParameters* Params )
{
	// Parse the parameters or copy for later usage.
}

void CalledOnBackgroundThread_TG()
{
	FSimpleDelegateGraphTask::CreateAndDispatchWhenReady
	(
		FSimpleDelegateGraphTask::FDelegate::CreateStatic( &SomeFunctionThatWillBeCalledOnGameThread, new FParameters() )
		, TEXT( "PassParamsToGrameThread" )
		, nullptr
		, ENamedThreads::GameThread
	);
}

// Lock free list.
TLockFreePointerList<FParameters> GMyGlobalParameters;

void ThisIsTickedOnTheMainThread()
{
	// Grab all parameters.
	TArray<FParameters*> MyParams;
	GMyGlobalParameters.PopAll( MyParams );
}

void CalledOnBackgroundThread_LFL()
{
	// Enqueue my parameters. 
	GMyGlobalParameters.Push( new FParameters() );
}

I hope this will help you :slight_smile:

This could potentially do the trick - one question though - instead of using FDelegate::CreateStatic, I should be able to invoke my existing module-wide delegate, correct? Or is wrapping that broadcast/invocation inside SomeFunctionThatWillBeCalledOnGameThread still necessary because my delegate has parameters, and neither FSimpleDelegateGraphTask or FDelegateGraphTask appear to support that?

You can pass all parameters in these tasks as in the regular delegate.