As it was said before, all operations take place in the main thread. However, some time-consuming operations are performed in asynchronous threads, resulting in full efficiency from the processor. However, when it comes to Unity3D, another asynchronous concept, which we call Coroutine, comes into our lives. But since there is no real multi-thread here, we cannot trust this system, so how do we do multi-threading in Unity?

Thread Review

We should explain a number of points regarding the multi-thread program:

  • ThreadStart: Creating an asynchronous thread in Unity is very simple, you can create a Thread directly using this class “System.Threading.Thread”. When the thread is started, it must do some work, these jobs are called methods in the programming area. You have to give the method name to the Thread’s constructor.
Worker workerObject = new Worker();
Thread workerThread = new Thread(workerObject.DoWork)
workerThread.Start();
  • Thread Termination: Even if there is an abort method to terminate the Thread, it is not recommended to use it because it does not stop the Thread immediately. If the thread has created unmanageable code, you have to wait for it to finish. The traditional way to stop Thread is to make it a state variable. Create a loop in the method you send to the Thread, and this loop will depend on this variable. If false, it exits the loop and the Thread terminates.
public class Worker
{
    public void DoWork()
    {
        while (!_shouldStop)
        {
            Console.WriteLine("worker thread: working...");
        }
        Console.WriteLine("worker thread: terminating gracefully.");
    }
    public void RequestStop()
    {
        _shouldStop = true;
    }
    private volatile bool _shouldStop;
}

Dispatcher

As you can see, we have made a variable that terminates the loop to terminate the running thread, and a public method to control the end.

  • Shared Data Transactions: This is where the most problems with Multi-Thread are encountered. Let’s consider a scenario; Thread_A and Thread_B are trying to use the same variable at the same time, what would it be worth? Although this problem is generally tried to be solved with the lock keyword, C# offers us another alternative, the volatile keyword. When we specify this to the variable, we tell the processor to return the last value and not read the cache.

If you have experience with another platform, for example iOS or WPF, you know Dispatcher. Simply each Thread has its own unique Dispatcher. For example, in WPF, we do the following to access UI controls from another Thread:

Thread thread=new Thread(()=>{
	this.Dispatcher.Invoke(()=>{
        //UI
		this.textBox.text=...
		this.progressBar.value=...
    });
});

However, the annoying point is; Unity doesn’t provide a Dispatcher! We can design our own Dispatcher by addressing one point:

  • Our Dispatcher must be a MonoBehaviour because the UI controls are on the Main Thread.
  • When updating, consider the Producer-Consumer model, there is a task that will only update the UI.
  • There is a method run in Unity, it is Update.

Starting from this, UnityDispatcher must take an Action delegate with a BeginInvoke method to run it.

public void BeginInvoke(Action action){
	while (true) {
		//The main thread is not recommended to use the lock keyword to prevent the block thread from becoming deadlock
		if (0 == Interlocked.Exchange (ref _lock, 1)) {
			//acquire lock
			_wait.Enqueue(action);
			_run = true;
			//exist
			Interlocked.Exchange (ref _lock,0);
			break;
		}	
	}
}

Just to be clear, as seen here Queue is not Thread safe, so it needs a lock. However, since Main Thread is too heavy, we used Interlocked.exchange instead of preferring this.

Using Coroutine and MultiThreading

From what you have understood so far, you can clearly see that Coroutine and Threading are not mutually exclusive concepts. In nested use, a Coroutin can be created to wait until a Thread finishes their work. Below is the core part of our design. The WaitFor method asks Thread every frame if it’s finished:

public bool Update(){
        if(_isDown){
                OnFinished ();
                return true;
        }
        return false;
}
public IEnumerator WaitFor(){
        while(!Update()){
                //Pause the cooperative program, 
                //and continue to execute the next frame
                yield return null;
        }
}

Then WaitFor in any UI Thread will result as asynchronous Thread. You should remember that we have to start with StartCoroutine here:

void Start(){

    Debug.Log("Main Thread :"+Thread.CurrentThread.ManagedThreadId+" work!");
    StartCoroutine (Move());
}

IEnumerator Move()
{
    pinkRect.transform.DOLocalMoveX(250, 1.0f);
    yield return new WaitForSeconds(1);
    pinkRect.transform.DOLocalMoveY(-150, 2);
    yield return new WaitForSeconds(2);
    //AI operation, falling into deep thought, 
    //executing in an asynchronous thread,
    //GreenRect will not be stuck
    job.Start();
    yield return StartCoroutine (job.WaitFor());
    pinkRect.transform.DOLocalMoveY(150, 2);

}