📜 ⬆️ ⬇️

Multithreading in Unity by means of reactive extensions



This article will discuss the main problems encountered in the development of multi-threaded mobile games using Unity, as well as ways to solve them using UniRx (reactive extensions for Unity).

The article consists of two parts. The first is devoted to multithreading for the “smallest”, it tells in an accessible language about streams and how to create them, about stream synchronization. The second part is devoted to reactive extensions, their device, principle of operation and methods of application.

Since one of the languages ​​for writing scripts in Unity is C #, in which we develop applications, all the code will be written only in it. For an in-depth understanding of the principles of multithreading and reactive extensions, we recommend reading the basics of multithreading and what is reactive extensions . If the reader is familiar with this topic, then you can skip the first section.
')

Multithreading for the smallest


Multi-threaded applications are applications that perform several tasks simultaneously in separate threads. Applications that use multi-threading, more quickly respond to user actions, because the user interface remains active, while tasks that require intensive work of the processor are performed in other threads. Multi-threaded C # applications using Mono are developed using keywords: Thread, ThreadPool, and asynchronous delegates.

Let's look at a multithreaded application using the example of a build. Suppose that each worker performs his duties simultaneously with other workers. For example, one washes the floors, another washes the windows, etc. (and it all happens at the same time). These are our streams.



Thread is a class that allows you to create new threads within an existing application.
Asynchronous delegates - an asynchronous method call using a delegate that is defined with the same signature as the method being called. For an asynchronous method call, you must use the BeginInvoke method. With this approach, the delegate takes a thread from the pool and executes some code in it.

ThreadPool - implementation of the “object pool” pattern. Its meaning is in effective flow management :: creating, deleting, assigning to it some kind of work. Returning to the construction analogy, ThreadPool is a foreman who controls the number of builders at a construction site and assigns each of them a task.



Thread synchronization tools


The C # language provides tools for synchronizing threads. These tools are presented in the form of lock and Monitor. They are used to ensure that the execution of a block of code is not carried out simultaneously by several threads. But there is one nuance. Using these tools can lead to deadlock (deadlock threads). It happens like this: thread A waits for thread B to return control, and thread B, in turn, waits for thread A to execute the blocked code. Therefore, multithreading and synchronization of threads must be used with caution.

Problems of the built-in mechanisms of a multithreading in Unity


The main problem we face when developing single-threaded applications is UI-friezes, caused by performing complex operations in the main thread. Unity has a task parallelization mechanism, presented in the form of coroutine (coroutine), but it works in one thread, and if you run something “heavy” in coroutine - hello, freeze. If we are satisfied with the parallel execution of functions in the main thread, then we can use korutiny. There is nothing difficult in this, in the Unity documentation this topic is very well covered. However, I would like to remind you that corutins are iterators, which work as follows in Unity:


In addition to the benefits, Korutin also have disadvantages:

  1. Unable to get return value

    private IEnumerator LoadGoogle() { var www = new WWW("http://google.com"); yield return www; //  www.text    . } 
  2. Error processing

     private IEnumerator LoadGoogle() { try { var www = new WWW("http://google.com"); yield return www; } catch { yield return null; } } 
  3. Crutches with callbacks

     private IEnumerator LoadGoogle(Action<string> callback) { var www = new WWW("http://google.com"); yield return www; if (callback != null) { callback(www.text); } } 
  4. Do not handle heavyweight methods in corintry

      void Start() { Debug.Log(string.Format("Thread id in start method = {0}", Thread.CurrentThread.ManagedThreadId)); StartCoroutine(this.HardMethod()); } private IEnumerator HardMethod() { while (true) { Thread.Sleep(1001); Debug.Log(string.Format("Thread id in HardMethod method = {0}", Thread.CurrentThread.ManagedThreadId)); yield return new WaitForEndOfFrame(); } } //Output: //Thread id in start method = 1 //Thread id in HardMethod method = 1 //Thread id in HardMethod method = 1 //Thread id in HardMethod method = 1 

As mentioned earlier, the cortinos are run in the main thread. For this reason, we get friezes by running heavy methods in them.

A number of these shortcomings are easily eliminated with the help of reactive extensions, which in the future will bring many more different features and facilitate development.

What is reactive expansion?


Reactive extensions - a set of libraries that allow you to work with events and asynchronous calls in the style of Linq. The task of such extensions is to simplify the writing of code, in which asynchronous interaction appears. Unity uses the UniRx library, which provides the basic functionality of reactive extensions. UniRx - implementation of reactive extensions for Unity based on .NET Reactive Extensions. Why not use this native implementation? Because standard RX in Unity do not work. The library is cross-platform and is supported on PC / Mac / Android / iOS / WP8 / WindowsStore platforms.

What provides us UniRx?

How it works?


The basis of reactive extensions are the IObserver and IObservable . They provide a generic mechanism for push notifications, also known as the “Observer” design pattern.

Source: https://habr.com/ru/post/268781/


All Articles