Let’s say I have a program with many functionalities. One of them is a “generate list of employees”, which, well, generates a list of employees. This method creates very detailed information about those employees, and runs some simulations, so it’s not instantly ready. However, this method runs in the same scene as other lightweight operations, say “change company name”.
How can I run this method asynchronously, so that I can pass some % of completion around and have the user see a “progress bar” on the employee generation somewhere on the screen, while still smoothly interacting with other parts of the program?
The first is coroutines. Coroutines still run on the main thread but they allow you to break chunks of work up across multiple frames through the “yield return” mechanism of iterator methods in C#. So let’s say you want to generate 1000 employees, you can use a coroutine to generate just one or a small number of employees every frame rather than doing it all at once. This will spread the work out and allow your game to continue running smoothly over the course of the employee generation process.
The second option is to use real multithreading. All of the multithreading libraries of C# are available to you in Unity. The benefit of real multithreading is that it allows you to better utilize multiple cores of the player’s device CPU. The drawbacks though are that multithreaded code is a bit more complicated to get right. First of all, Unity generally prohibits you from accessing or modifying any Unity objects in other threads. So to get around this you must do your processing using all of your own objects (no GameObjects, no Components, no MonoBehaviours) in your other thread. Then you must bring the data back to the main thread to interact with Unity objects.
As I see, in this approach I’d have to run tests to see how many of those employees can be generated in a single frame without lagging the software, then implement this as a factor to see where in the IEnumerator method to put a “yield return” (probably WaitForEndOfFrame, right? or just “yield return null”?). Is that about right?
This seems to be much more performance efficient if the operation in question is truly heavy. Is this true? How could I get started on using those libraries? Where should I look?
Yep! yield return null is the way to wait until next frame. You could structure your coroutine like this for easy tweaking:
IEnumerator CreateEmployees(int totalEmployees, int employeesPerFrame) {
int generatedThisFrame = 0;
for(int i = 0; i < totalEmployees; i++) {
GenerateSingleEmployee();
generatedThisFrame++;
if (generatedThisFrame >= employeesPerFrame) {
generatedThisFrame = 0;
// Wait one frame
yield return null;
}
}
}
Then you can just change the “employeesPerFrame” variable as you see fit. (Also I just wrote this off the cuff, not guaranteed to work properly or even compile)
Yes it has the potential to be much more performant. It’s also more complicated.
There is also a third possibility, you can look into the Unity Job System: https://docs.unity3d.com/Manual/JobSystem.html. I’m personally not very knowledgable about this system, and I’m not sure if it’s possible to use it outside of the context of ECS.
Thanks again! Very through explanation. The code you wrote is also very simple to understand.
I’ll analyse how performance heavy the application I’m developing will be before deciding on which of those alternatives to use.
I’ve done a quick search about the ECS system, and maybe I’ll work with it instead of the traditional monobehaviour scripting, since the application is data oriented. The big pro is that it’ll probably be way more efficient, and this is a concern since the main target platforms are mobile. The big con is that it’s in a preview state, and as a non-very experienced programmer I may not be able to keep up with the changes that may arise as the system updates.