Search Unity

  1. Unity 6 Preview is now available. To find out what's new, have a look at our Unity 6 Preview blog post.
    Dismiss Notice
  2. Unity is excited to announce that we will be collaborating with TheXPlace for a summer game jam from June 13 - June 19. Learn more.
    Dismiss Notice

Question How to run an asynchronous operation within a single scene?

Discussion in 'Scripting' started by InacioM, Jul 9, 2020.

  1. InacioM

    InacioM

    Joined:
    Jun 19, 2019
    Posts:
    36
    Hello, all!

    Let me elaborate my question.

    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?
     
  2. PraetorBlue

    PraetorBlue

    Joined:
    Dec 13, 2012
    Posts:
    7,931
    You have two basic approaches available to you:

    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.
     
    InacioM likes this.
  3. InacioM

    InacioM

    Joined:
    Jun 19, 2019
    Posts:
    36
    Thanks for the quick answer!

    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?
     
  4. PraetorBlue

    PraetorBlue

    Joined:
    Dec 13, 2012
    Posts:
    7,931
    Yep!
    yield return null
    is the way to wait until next frame. You could structure your coroutine like this for easy tweaking:

    Code (CSharp):
    1. IEnumerator CreateEmployees(int totalEmployees, int employeesPerFrame) {
    2.   int generatedThisFrame = 0;
    3.   for(int i = 0; i < totalEmployees; i++) {
    4.     GenerateSingleEmployee();
    5.     generatedThisFrame++;
    6.     if (generatedThisFrame >= employeesPerFrame) {
    7.       generatedThisFrame = 0;
    8.       // Wait one frame
    9.       yield return null;
    10.     }
    11.   }
    12. }
    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.

    My personal favorite is to use a combination of Task.Run: https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.task.run?view=netcore-3.1
    and get results back into your main thread with the use of a ConcurrentQueue. You can enqueue your results from the task thread and check the queue for results in an Update() method safely using TryDequeue: https://docs.microsoft.com/en-us/do..._Concurrent_ConcurrentQueue_1_TryDequeue__0__

    There is also the entire System.Threading namespace: https://docs.microsoft.com/en-us/dotnet/api/system.threading?view=netcore-3.1. The most basic way to start a thread is directly through the Thread class: https://docs.microsoft.com/en-us/dotnet/api/system.threading.thread?view=netcore-3.1

    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.
     
    Last edited: Jul 9, 2020
    InacioM likes this.
  5. InacioM

    InacioM

    Joined:
    Jun 19, 2019
    Posts:
    36
    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.
     
    PraetorBlue likes this.