Async Texture2D Import Garbage Collection Crash

Hi all

Project Background

I am working on an application that requires textures to be imported during runtime. Due to the loading times of importing all the textures, I run the operation through an Async task in which the 2DTextures get returned to the game thread via a TFuture.

The Problem

Around 1 in 6 run-throughs I get a crash whilst attempting to import the very last texture. It is caused by an checkf inside UObjectClogals.cpp line 2285:

checkf(!IsGarbageCollecting(), TEXT("Unable to create new object: %s %s.%s. Creating UObjects while Collecting Garbage is not allowed!"),
	*GetNameSafe(InClass), *GetPathNameSafe(InOuter), *InName.ToString());

This happens during the NewObject call inside Texture2D.cpp

Before attempting to import the texture, I check to ensure that garbage collection is currently not active, if so, do not import. When running in the editor, this check works and the 1-6 play throughs will catch the error and exit without hitting the assertion. However, in packaged builds, my check for IsGargbageCollecting() returns false whilst the check inside the call to create the new Texture2D object returns true.

Whilst I am calling this logic, there isn’t anything going on in the world, so I am not sure what is being garbage collected and why it is only affecting the last texture I am attempting to import.

I have tried adding a “dummy” texture at the end to see I could use that as a dirty workaround, sadly no success.

The Code

All logic is handled in a custom GameInstance. I will include some code snippets of my current implementation although note, some variables/names have been changed.

void UMyGameInstance::ImportImageAssets()
{
	UE_LOG(LogCoreGI, Log, TEXT("Initiating image asset import process..."));

	OnImportedAllImageAssetsOperationComplete.AddDynamic(this, &UMyGameInstance::ImportImageAssetsComplete);

	TArray<FAssetData> allAssetData;

	//Code here to generate the asset array

	if (allAssetData.Num() == 0) { return; }

	futureReturn = ImportImageAssetsAsync(this, allAssetData, GetActiveContent().contentPathName, [this]()
	{
		if (futureReturn.IsValid())
		{
			AsyncTask(ENamedThreads::GameThread, [this]() { OnImportedAllImageAssetsOperationComplete.Broadcast(futureReturn.Get()); });
		}
	});
}


TFuture<FImportImageWrapper> UMyGameInstance::ImportImageAssetsAsync(UObject* outer, const TArray<FAssetData> allAssetData, const FString contentPackageName, TFunction<void()> OnImportAssetsOperationComplete)
{
	return Async(EAsyncExecution::ThreadPool, [=]() {return ImportImageAssetsAsyncOperation(outer, allAssetData, contentPackageName); }, OnImportAssetsOperationComplete);
}


FImportImageWrapper UMyGameInstance::ImportImageAssetsAsyncOperation(UObject* outer, const TArray<FAssetData> allAssetData, const FString contentPackageName)
{
	UPROPERTY()
	FImportImageWrapper result;


	for (FAssetData asset : allAssetData)
	{
		FString filePath = UFunctionLibrary::GetContentDataDir(contentPackageName) + asset.assetDir + "/" + asset.assetGUID + ".png";

		bool bIsGCInProgress = IsGarbageCollecting();

		UE_LOG(LogCoreGI, Log, TEXT("Starting next texture import. Is GC: %s"), bIsGCInProgress ? TEXT("TRUE") : TEXT("FALSE"));

		if (!bIsGCInProgress)
		{
			importedTexture = UKismetRenderingLibrary::ImportFileAsTexture2D(outer->GetWorld(), filePath);

			if (IsValid(importedTexture))
			{
				result.imageAssets.Add(asset.assetGUID, importedTexture);
				UE_LOG(LogCoreGI, Log, TEXT("Successfully imported %s"), *filePath);
			}
			else
			{
				UE_LOG(LogCoreGI, Warning, TEXT("Failed to import %s invalid return"), *filePath);
			}
		}
		else
		{
			UE_LOG(LogCoreGI, Warning, TEXT("Failed to import %s is garbage collecting"), *filePath);
			FImportImageWrapper failedResult; 
			failedResult.imageAssets.Add("GCFailure", nullptr);
			return failedResult;
		}
	}

	UE_LOG(LogCoreGI, Warning, TEXT("Asset Image Import: Async operations successfulled imported %d assets"), result.imageAssets.Num());

	return result;
}


void UMyGameInstance::ImportImageAssetsComplete(FImportImageWrapper outImportedImageAssets)
{
	if (outImportedImageAssets.imageAssets.Contains("GCFailure"))
	{
		UE_LOG(LogCoreGI, Error, TEXT("Failed to import image assets due to garbage collection procedure. "));
		OnImportedAllImageAssets.Broadcast(false);
		return;
	}

	importedImageAssets = outImportedImageAssets.imageAssets;

	UE_LOG(LogCoreGI, Warning, TEXT("Game Instance received %d assets. Registered %d assets"), outImportedImageAssets.imageAssets.Num(), importedImageAssets.Num());

	OnImportedAllImageAssets.Broadcast(true);
}

I have tried to safeguard the code as much as possible, however, I am not sure what other checks I can perform to handle the garbage collection assertion.

Any ideas would be greatly appreciated.

Thanks in advance.

Alex