Multithread Pathfinding, Unit can't receive path array result,MultiThread Pathfinder, Unit can't receive the path array

I have a pathfinder program using A* that i made following a tutorial. It works fine until i tried to make it multithread using async Task. The Thread works and the path is calculated, but when the path result is being passed to the Unit object, for some resaon only one or two Unit that receive the path array. where did i do wrong ?

ThreadRequestManager :

public class ThreadRequestManager : MonoBehaviour {

    Queue<PathResult> results = new Queue<PathResult>();

    static ThreadRequestManager instance;
    ThreadPathfinding pathfinding;

    private void Awake()
    {
        instance = this;
        pathfinding = GetComponent<ThreadPathfinding>();
    }

    private void Update()
    {
        if (results.Count > 0)
        {
            int itemsInQueue = results.Count;
           
            lock (results)
            {
                for(int i = 0; i < itemsInQueue; i++)
                {
                    PathResult result = results.Dequeue();
                    result.callback(result.path, result.success);
                }
            }
        }
    }

    public static async Task RequestPath(PathRequest request)
    {
        await Task.Run(() => instance.pathfinding.FindPath(request, instance.FinishedProcessingPath));
    }

    public void FinishedProcessingPath(PathResult result)
    {
        lock(results)
        {
            results.Enqueue(result);
        }
    }

}

public struct PathResult
{
    public Vector3[] path;
    public bool success;
    public Action<Vector3[], bool> callback;

    public PathResult(Vector3[] path, bool success, Action<Vector3[], bool> callback)
    {
        this.path = path;
        this.success = success;
        this.callback = callback;
    }
}
public struct PathRequest
{
    public Vector3 pathStart;
    public Vector3 pathEnd;
    public Action<Vector3[], bool> callback;

    public PathRequest(Vector3 _start, Vector3 _end, Action<Vector3[], bool> _callback)
    {
        pathStart = _start;
        pathEnd = _end;
        callback = _callback;
    }
}

Units :

public class ThreadUnits : MonoBehaviour {

    public Transform target;
    float speed = .5f;
    public Vector3[] path;
    int targetIndex;

    // Use this for initialization
    void Start()
    {
        ThreadRequestManager.RequestPath(new PathRequest(transform.position, target.position, OnPathFound));
    }

    public void OnPathFound(Vector3[] newPath, bool pathSuccess)
    {
        if (pathSuccess)
        {
            path = newPath;
            // FollowPath();
            StopCoroutine("FollowPath");
            StartCoroutine("FollowPath");
        }
    }

    IEnumerator FollowPath()
    {
        Vector3 currentWaypoint = path[0];
        while (true)
        {
            if (transform.position == currentWaypoint)
            {
                targetIndex++;

                if (targetIndex >= path.Length)
                {
                   yield break;
                }
                currentWaypoint = path[targetIndex];
            }
            transform.position = Vector3.MoveTowards(transform.position, currentWaypoint, speed);
            yield return null;
        }
    }
    public void OnDrawGizmos()
    {
        if (path != null)
        {
            for (int i = targetIndex; i < path.Length; i++)
            {
                Gizmos.color = Color.black;
                Gizmos.DrawCube(path*, Vector3.one);*

if (i == targetIndex)
{
Gizmos.DrawLine(transform.position, path*);*
}
else
{
Gizmos.DrawLine(path[i - 1], path*);*
}
}
}
}
}
Pathfinder :
public class ThreadPathfinding : MonoBehaviour {

Grid grid;

void Awake()
{
grid = GetComponent();
}

public void FindPath(PathRequest request, Action callback)
{
Stopwatch sw = new Stopwatch();
sw.Start();

Vector3[] waypoint = new Vector3[0];
bool pathSuccess = false;

Node startNode = grid.NodeFromWorldPoint(request.pathStart);
Node targetNode = grid.NodeFromWorldPoint(request.pathEnd);

print(“thread :” + Thread.CurrentThread.ManagedThreadId);

if (startNode.walkable && targetNode.walkable)
{
Heap openSet = new Heap(grid.MaxSize);
HashSet closedSet = new HashSet();
openSet.Add(startNode);

while (openSet.Count > 0)
{
Node currentNode = openSet.RemoveFirst();
closedSet.Add(currentNode);

if (currentNode == targetNode)
{
sw.Stop();
print("path found: " + sw.ElapsedMilliseconds);
pathSuccess = true;
break;
}

foreach (Node neighbour in grid.GetNeighbours(currentNode))
{
if (!neighbour.walkable || closedSet.Contains(neighbour))
{
continue;
}
int newMovCostToNeighhbour = currentNode.gCost + GetDistance(currentNode, neighbour);
if (newMovCostToNeighhbour < neighbour.gCost || !openSet.Contains(neighbour))
{
neighbour.gCost = newMovCostToNeighhbour;
neighbour.hCost = GetDistance(neighbour, targetNode);
neighbour.parent = currentNode;

if (!openSet.Contains(neighbour))
{
openSet.Add(neighbour);
}
else
{
openSet.UpdateItem(neighbour);
}
}
}
}
}
if (pathSuccess)
{
waypoint = RetracePath(startNode, targetNode);
}
callback(new PathResult(waypoint, pathSuccess, request.callback));
}

Vector3[] RetracePath(Node startNode, Node endNode)
{
List path = new List();
Node currentNode = endNode;

while (currentNode != startNode)
{
path.Add(currentNode);
currentNode = currentNode.parent;
}
Vector3[] waypoints = SimplifyPath(path);
Array.Reverse(waypoints);
return waypoints;
}

Vector3[] SimplifyPath(List path)
{
List waypoints = new List();
Vector2 directOld = Vector2.zero;

for (int i = 1; i < path.Count; i++)
{
Vector2 directNew = new Vector2(path[i - 1].gridX - path_.gridX, path[i - 1].gridY - path*.gridY);
if (directNew != directOld)
{
waypoints.Add(path[i-1].worldPosition);
}
directOld = directNew;
}
return waypoints.ToArray();
}*_

int GetDistance(Node nodeA, Node nodeB)
{
int disX = Mathf.Abs(nodeA.gridX - nodeB.gridX);
int disY = Mathf.Abs(nodeA.gridY - nodeB.gridY);

if (disX > disY)
return 14 * disY + 10 * (disX - disY);
return 14 * disX + 10 * (disY - disX);
}
}

Well, is your “pathfinding” class even thread safe? We can see that you have only one instance of your path finding class that is reused in every request. Are you sure that the FindPath class does not use / modify any shared resources?

edit

Apart from your problem (which we can’t solve without knowing the implementation of your pathfinding) locks should generally be hold only as long as necessary. In your case you should just pull the results into a second collection and release the lock. After that you can actually process the results outside the lock.

Also even you have a lock in place you still are up for a race condition because you read the itemsInQueue before you lock the results. So in between reading the Count and entering the lock another thread could add another result. This is not really an issue in your case as the new missed result would simply be processed the next frame. However it’s generally just a bad design. You really need to be careful when using threading.

Finally while a Queue<> Is quite handy when you dynamically want to queue elements and dequeue them. However in case of a pure transfer queue a simple List will work as well. The Queue uses internally a ring buffer which is unnecessary since when you process the results you always empty the queue. So two lists would be the best solution. Something like this:

List<PathResult> results = new List<PathResult>();
List<PathResult> results2 = new List<PathResult>();

private void Update()
{
    if (results.Count > 0)
    {
        lock (results)
        {
            results2.AddRange(results); // just copy the current results over
            results.Clear(); // clear the list
        }
        foreach(var result in results2) // work on our seperate list outside the lock
        {
            result.callback(result.path, result.success);
        }
        results2.Clear();
    }
}

Though again, your actual issue is most likely that your pathfinding is not threadsafe.