Search code examples
c#multithreading

Run multiple threads at once, return their results and use them to do something


I've been trying to write a chunk loading system for a game I'm making in Godot. For that I want to use threads to load multiple chunks at once.

int threadsToUtilise = 7;

Thread[] threads; // array to store threads

ArrayMesh[] threadResults; // array to save meshes returned by WorldLoader.GetChunk();

Vector3I[] chunkIDsLoading; // array to save position (Vector3I) of the chunk thread[i] is loading; serves to later position the mesh at the right position

// the arrays are initialised with a size of threadsToUtilise (7)

List<Vector3I> chunksToLoad = new(); // List to store positions of all chunks that still need to be loaded in

public override void _Process(double delta) // method runs every frame
{
    for (var i = 0; i < threadsToUtilize; i++)
    {
        if (threads[i] == null){
            if (chunksToLoad.Count == 0) break;
            threads[i] = new Thread(() => { threadResults[i] = WorldLoader.GetChunk(chunksToLoad[0], false); });
            threads[i].Start();
            chunkIDsLoading[i] = chunksToLoad[0];
            chunksToLoad.RemoveAt(0);
        }
        else if (!threads[i].IsAlive) {
            if (threadResults[i] != null) {
            // implement some stuff
            }
            threads[i] = null;
            threadResults[i] = null;
            chunkIDsLoading[i] = Vector3I.Zero;
        }
    }
}

Generally I wanted to load a chunk and as soon as the mesh is created, implement it. It more or less works but there are two main problems:

  1. Sometimes, very randomly, get an error: Index was outside the bounds of the array. This happens in the line, where the thread is created and threadResults[i] = WorldLoader.GetChunk... is assigned. But that shouldn't be the case, because the arrays are created with the size of threadsToUtilise and the loop is depending on it, too.
  2. Sometimes the chunks are placed correctly, another time they are offset from the position they are supposed to be in (about 1 or 2 chunkpositions too early/late). The method creating the meshes works fine, I did test that one.

Solution

  • This looks like a "capture" problem; try:

    var idx = i;
    var chunk = chunksToLoad[0];
    threads[i] = new Thread(() => { threadResults[idx] = WorldLoader.GetChunk(chunk, false); });
    

    which creates snapshots of the values that we "capture" in the lambda. Otherwise, you are capturing the variable, not the value at the time you create the lambda - when the thread runs (at some undefined point in the future), the values at that time will be used, which isn't what you intended. The captured variables idx and chunk are scoped by their declaration location (put simply: the nearest {), which means you'll get a separate definition for each loop iteration.