I’ve been getting familiar with the intricacies of multithreading in UE, but have run into an issue where UE keeps nuking my data once the thread starts. Sometimes by replacing the first 8 bytes with \0
or sometimes just garbling the whole thing.
At first I was using a TArray and FCriticalSection but changed to a TQueue item as i though it was related to the array being resized, but that didn’t resolve the issue. I believe it’s GC related as when I perform a memcpy
on the data into a class variable the data isn’t molested, while the original queued data is.
I added two breakpoints in Visual studio; one in the DoWork
function at the line that reads auto result = Async(...
and one in the Async_WorkQueue
function at the first FPlatformProcess::Sleep(1);
line. When the breakpoints were hit, I watched the data
, dataCopyPtr
, and workQueue
variables using a static cast for readability.
Watch result at first breakpoint
//Breakpoint 1 - 0x00000a2a9abad740 "String Number One"
//Breakpoint 2 - 0x00000a2a9abad740 "String Number One"
static_cast<char*>(dataCopyPtr),17
//Breakpoint 1 - 0x000000971c5786d0 "String Number One"
//Breakpoint 2 - 0x000000971c5786d0 "Ðݸ~€\x2\0\0\0š\xfP€\x2\0\0@"
static_cast<char*>(workQueue.Head->Item.ptr),17
Below is an as much reduced example as I can make that still demonstrates my issue.
.h
#pragma once
#include "CoreMinimal.h"
#include "Containers/Queue.h"
#include "QueueOwner.generated.h"
UCLASS()
class UQueueOwner : public UObject {
GENERATED_BODY()
struct QueueItem {
public:
QueueItem() {};
QueueItem(void* ptr, size_t size) : ptr(ptr), size(size) {};
~QueueItem() { ptr = nullptr; size = 0; };
UPROPERTY()
void* ptr;
UPROPERTY()
size_t size;
};
private:
bool isWorking = false;
void* dataCopyPtr;
TQueue<QueueItem, EQueueMode::Mpsc> workQueue;
void Async_WorkQueue();
void OnQueueCompleted();
public:
void DoWork(void* data, size_t size);
void Test();
};
.cpp
UE_DISABLE_OPTIMIZATION
#include "QueueOwner.h"
DEFINE_LOG_CATEGORY_STATIC(LogTesting, Log, All);
void UQueueOwner::Test(){
FString strA = UTF8TEXT("String Number One");//17
//Convert to UTF8, deal with encodings when it works...
FStringView strViewA = strA;
const auto utf8StringA = StringCast<UTF8CHAR>(strViewA.GetData());
DoWork((UTF8CHAR*)utf8StringA.Get(), utf8StringA.Length() * sizeof(UTF8CHAR));
}
void UQueueOwner::DoWork(void* data, size_t size) {
//Straight up copy the data as I'm out of ideas on how to protect it from UE.
dataCopyPtr = FMemory::Malloc(size);
FMemory::Memcpy(dataCopyPtr, data, size);
QueueItem* item = new QueueItem(data, size);
//Enqueue the data struct Same issue whether MoveTemp or not
//if (!workQueue.Enqueue(MoveTemp(*item))) {
if (!workQueue.Enqueue(*item)) {
//Handle failure
return;
}
if (!isWorking) {
isWorking = true;
auto result = Async(EAsyncExecution::ThreadPool, [&]() { Async_WorkQueue(); }, [&]() { OnQueueCompleted(); });
}
}
void UQueueOwner::Async_WorkQueue() {
//Make this test a slow to initialize task
FPlatformProcess::Sleep(1);
size_t totalSize = 0;
QueueItem item;
while (workQueue.Dequeue(item)) {
totalSize += item.size;
//Do something with item.ptr;
FPlatformProcess::Sleep(0.1);
}
}
void UQueueOwner::OnQueueCompleted() {
isWorking = false;
}