Before we get into this topic, we should understand the Single-Thread models of Client applications. Main Thread or UI Thread this is the thread where the UI controls are created. On the server side, our applications are multi-channel because User-A should not wait while User-B is working, but synchronization and concurrency are a headache in such applications.
Is Multi-Thread Needed?
In a multi-channel program, we should know that; As hardware, the processor can run a single Thread at the same time, but a certain time passes for the Threads. This work is so fast that we see it as a synchronicity. The need on the client side depends on the complexity of your application, as technology is no longer a bottleneck today:
- If there is no time-consuming task such as network, IO, AI, try not to use multi-threading because cross-thread operations do not have permission to access the UI thread and data synchronization becomes an annoying problem. Also it is very easy to lock the UI with “lock” or “deadlock”.
- Conversely, if the application is complex, it would be correct to reduce the pressure on the Main Thread with multi-channel.
Also, it shouldn’t spam Thread, too many threads slow down CPU operation, in these cases it should use ThreadPool or Threads that GC returns.
Coroutine Internal Principles
If we go back to Unity3D; Unity applications are also single-thread but for asynchronous operations Coroutine is provided by Unity3D. Coroutine actually executes in the main thread. It consists of Coroutine Ienumarator interface and Yield iterator block in Unity. With the Enumarator interface, Ienumarator includes three methods:
- Current: Returns the object in its current position in the Collection.
- MoveNext: takes the iterator to the next position in the collection, checks whether the collection is exceeded with the bool it returns.
- Reset: Reset the current state.
Yield, this is a bit of a confusing technology because the compiler does a lot of work for us, we can’t see its internal implementation. It is confusing as to the meaning of the word. Therefore, it would be more correct to say branching, taking out. In C#, built-in Collections are enumarable, that is, they implement the Ienumarable interface, so we can iterate with a foreach even in an array.
Let’s imagine that we will do the above logic ourselves; we need to provide a customized enumerator for this job, and for this we need to implement the Ienumerator interface.
Now let’s implement the AnimalEnumerator iterator.
If you understand what has happened so far, you can easily understand what yield does in the rest. You will also understand why the Coroutine in Unity is a so-called multi-thread. Below is a simple piece of code:
Did you notice the return type of the method? You may have noticed that you did not define an enumerator and did not implement the required interface. The problem lies in the yield keyword. C# has been using this keyword since 2.0 to simplify these operations and create enumerator blocks for you. The compiler creates the necessary iterators for us. Now let’s decompile it:
Let’s sharpen our lessons:
- The yield keyword is a syntactic, so the compiler cannot see exactly what you are doing.
- Compiler internally creates enumeration class “
d__1" When Yield return is used as an enumeration, Current gets the result via MoveNext.
You should have a clear understanding of a number of things about Coroutine in Unity. I mentioned branching/exporting for yield earlier. Let’s see what I mean by this:
- Branching: As we understand from the code we decompile, the compiler is proceeding by branching between the switch and the states.
- Exporting: It exports a data by writing the obtained object before each branch to Current.