Checking if C# code is thread safe

Hi there,

I have written a small program where a capsule moves from one quad to the other and upon reaching one; it stops and rotates to face the next quad and then moves towards it.

I decided to make the program multi threaded as I wanted to test multi threading in unity. I have used the thread signaling methodology using EventWaitHandles to establish a communication between 2 child threads and the main thread. The code runs fine so far with some resources being shared among different threads but I cant tell for sure if the program is thread safe or not.

I know that debugging multi threaded programs are a nightmare and unfortunately there isn’t a better way to debug these kinds of programs in unity without “Debug.Log()/print()” statements and afaik visual studio doesn’t have a thread debugger for unity as it does for conventional C# programs.

Therefore, I need some help to determine if my code is thread safe or not.

Thanks :slight_smile:

using UnityEngine;
using System.Collections;
using System.Threading;

public class Dummy : MonoBehaviour
{
    static EventWaitHandle ewh1 = new EventWaitHandle(false, EventResetMode.AutoReset);
    static EventWaitHandle ewh2 = new EventWaitHandle(false, EventResetMode.AutoReset);

    Thread t1, t2;

    [SerializeField]
    Transform waypoints;

    Vector3[] wayPointQuads;
    Vector3 capsulePosition;

    bool stopThread;
    bool gotNewData;

    int index = 0;

    [SerializeField]
    float moveSpeed, rotateSpeed;
    float turnAngle;

    private void Start()
    {
        capsulePosition = transform.position;

        SetUpArray();

        t1 = new Thread(SetWayPointData);
        t2 = new Thread(NotifyMainThread);

        index = 0;

        t1.Start();
        t2.Start();

        StartCoroutine("StartThread");
    }

    private void Update()
    {
        capsulePosition = transform.position;

        if (gotNewData)
        {
            RotateCapsule();

            ewh2.Set();
        }
        else transform.position += transform.forward * Time.deltaTime * moveSpeed;
    }

    void SetWayPointData()
    {
        ewh1.WaitOne();

        while (true)
        {
            if (stopThread) break;

            float distance = Vector3.Distance(capsulePosition, wayPointQuads[index % wayPointQuads.Length]);

            if (distance < 0.3f)
            {
                index++;
                Vector3 nextQuadDirection = (wayPointQuads[index % wayPointQuads.Length] - capsulePosition).normalized;
                turnAngle = 90 - Mathf.Atan2(nextQuadDirection.z, nextQuadDirection.x) * Mathf.Rad2Deg;

                WaitHandle.SignalAndWait(ewh2, ewh1);
            }
        }
    }

    void NotifyMainThread()
    {
        ewh2.WaitOne();

        while (true)
        {
            if (stopThread) break;

            WaitHandle.SignalAndWait(ewh1, ewh2);

            gotNewData = true;

            ewh2.WaitOne();
        }
    }

    IEnumerator StartThread()
    {
        for (int i = 0; i < 2; i++) yield return new WaitForEndOfFrame();

        ewh2.Set();
    }

    void SetUpArray()
    {
        wayPointQuads = new Vector3[4];
        int i = 0;
        foreach (Transform t in waypoints)
        {
            wayPointQuads[i] = t.position;
            i++;
        }
    }

    private void RotateCapsule()
    {
        float angleBetween = Vector3.Angle((wayPointQuads[index % wayPointQuads.Length] - transform.position).normalized, transform.forward);

        if (angleBetween <= 1) gotNewData = false;

        else
        {
            Vector3 targetDir = wayPointQuads[index % wayPointQuads.Length] - transform.position;

            Vector3 newDir = Vector3.RotateTowards(transform.forward, targetDir, rotateSpeed * Time.deltaTime, 0.0f);

            transform.rotation = Quaternion.LookRotation(newDir);
        }
    }


    private void OnDisable()
    {
        StopAllThreads();
    }

    private void OnDestroy()
    {
        StopAllThreads();
    }

    private void OnApplicationQuit()
    {
        StopAllThreads();
    }

    void StopAllThreads()
    {
        ewh1.Set();
        ewh2.Set();
        stopThread = true;
    }
}

Congratz on inventing a bicycle with some juicy spagetti on top.

I’d rather recommend using Jobs package for those things if you really need multithreading. Plus jobs could be burst-compiled.

Jobs are thread safe. Bonus points if you accomodate ECS + Burst.

Unity is single-threaded by design (excluding rendering and physics, which not, but handled internally).
Even if you didn’t ran into some troubles right away, you won’t be able to access anything Unity related from external threads.

3 Likes

Not anymore :slight_smile:

That’s exactly what I’ve pointed to. Plus it’s an external experimental package.
It doesn’t make the whole code base suddenly multithreaded.
And the classic approach still will be the same until ECS matures. Which will probably take no less than few years.