Search Unity

  1. Unity 2018.1 has arrived! Read about it here
    Dismiss Notice
  2. Scriptable Render Pipeline improvements, Texture Mipmap Streaming, and more! Check out what we have in store for you in the 2018.2 Beta.
    Dismiss Notice
  3. If you couldn't join the live stream, take a peek at what you missed.
    Dismiss Notice
  4. Improve your Unity skills with a certified instructor in a private, interactive classroom. Learn more.
    Dismiss Notice
  5. ARCore is out of developer preview! Read about it here.
    Dismiss Notice
  6. Magic Leap’s Lumin SDK Technical Preview for Unity lets you get started creating content for Magic Leap One™. Find more information on our blog!
    Dismiss Notice
  7. Want to see the most recent patch releases? Take a peek at the patch release page.
    Dismiss Notice

Does Combining Corouitines and Threads make sense?

Discussion in 'Scripting' started by HoloDeveleoper, May 16, 2018.

  1. HoloDeveleoper

    HoloDeveleoper

    Joined:
    May 14, 2018
    Posts:
    12
    Hello,

    i am not sure how to handle this Problem. Im very new to threading, and also never used Croutines in Unity.
    I have a Method wich is pretty expensive in a .dll that i call in an Update() every half a second. This makes my Screen lag for 50ms two times per second, wich is unacceptable.

    i try to Keep it simple in this example:

    Code (CSharp):
    1. private void LateUpdate() {
    2.         if (Time.time >= timeToGo)
    3.         {
    4.             runner.DoSomeWork();
    5.             timeToGo = Time.time + 0.5f;
    6.         }
    7.     }
    Code (CSharp):
    1.  
    2. private void DoSomeWork(){
    3. SomeExpensiveWork(ref Count, ref ids, ref vecs1, ref vecs2, ref vecs3);  //calls a .dll
    4.  
    5. SomeOtherWork(Count, ids, vecs1, vecs2, vecs3)
    6. }
    7.  
    SomeOtherWork is dependent on the Outputs of SomeExpensiveWork.
    My idea was to put "SomeExpensiveWork()" in a thread, and make "DoSomeWork()" a thread wich yields as long as the Thread is still running. I assume the Expensive work is threadsafe for this,
    I somehow think this is the wrong Approach even tho it sounds right in the Idea.
     
  2. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    5,571
    I'm assuming when you say this:
    You mean:
    In which case, yeah, that's a completely suitable way to deal with it.

    Some people have even created little libraries that allow the coroutine to jump back and forth between its own thread and the main thread. Like this one here called ThreadNinja:
    https://assetstore.unity.com/packages/tools/thread-ninja-multithread-coroutine-15717
     
    Kiwasi likes this.
  3. HoloDeveleoper

    HoloDeveleoper

    Joined:
    May 14, 2018
    Posts:
    12
    Yes thats what ment.
    Is this the usual way of handling such problems?

    thank u for ur fast response
     
  4. jschieck

    jschieck

    Joined:
    Nov 10, 2010
    Posts:
    318
    Yes, if you prefer using a more self-made approach or need to dive into how something like this would work, use object locks to make sure your threads and coroutines are in sync. For example

    Code (CSharp):
    1. using System.Collections;
    2. using System.Threading;
    3. using UnityEngine;
    4.  
    5. public class Test : MonoBehaviour
    6. {
    7.     private readonly object threadLock = new object();
    8.     private bool workReady = false;
    9.     private bool threadShutdown = false;
    10.     private int simulatedWork = 0;
    11.  
    12.     private void OnEnable()
    13.     {
    14.         workReady = false;
    15.         threadShutdown = false;
    16.         Thread workerThread = new Thread(DoThreadedWork);
    17.         workerThread.Start();
    18.         StartCoroutine(WaitForThreadWork());
    19.     }
    20.     private void OnDisable()
    21.     {
    22.         threadShutdown = true;
    23.     }
    24.     private IEnumerator WaitForThreadWork()
    25.     {
    26.         while (!threadShutdown)
    27.         {
    28.             // wait for the threaded work to complete
    29.             while (!workReady)
    30.                 yield return null;
    31.             // assuming this needs to happen on the main thread
    32.             // don't let the thread edit our data while we process
    33.             lock (threadLock)
    34.             {
    35.                 //SomeOtherWork(Count, ids, vecs1, vecs2, vecs3);
    36.                 Debug.LogFormat("Simulating some other work {0}", simulatedWork);
    37.                 workReady = false;
    38.             }
    39.         }
    40.     }
    41.     private void DoThreadedWork()
    42.     {
    43.         while (!threadShutdown)
    44.         {
    45.             lock (threadLock)
    46.             {
    47.                 //SomeExpensiveWork(ref Count, ref ids, ref vecs1, ref vecs2, ref vecs3);
    48.                 Thread.Sleep(50); // simulating expensive work for 50ms
    49.                 Interlocked.Increment(ref simulatedWork);
    50.                 workReady = true;
    51.             }
    52.             // wait half a second to do work again
    53.             Thread.Sleep(500);
    54.         }
    55.     }
    56. }
    57.  
     
  5. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    5,571
    'usual'... eh.

    There's lot of ways to deal with the problem. And since I don't have specifics about your problem, I could say exactly what the 'usual' would be... nor what I would even do.

    I would say it's not unusual to do it this way.

    Note, there are newer features that Unity has added, and some people may jump in with suggestions of them. Such as the new 'jobs' system:
    https://github.com/Unity-Technologi...ob/master/Documentation/content/job_system.md

    But the job system has a very specific use case. And it usually doesn't really consist of calling outside dll's. So it probably wouldn't meet your needs.

    But again, who knows, maybe it does. Maybe whatever your external dll does could be refactored into your local domain and written as a job.

    :shrugs:
     
  6. HoloDeveleoper

    HoloDeveleoper

    Joined:
    May 14, 2018
    Posts:
    12
    @lordofduct i was sitting for a day learning the new Job System but it seems to me that this is not Fitting my Problem since i work with references, and Jobs dont like references. Also big thanks for showing me the asset "Thread ninja", seems very interesting and Fitting for my Needs.

    @jschieck thank u for your example. i guess i cant get around using object locks since i call the root method every 0.5 sek. I Need a bit more time to read into your example Code tho.
     
  7. Doug_B

    Doug_B

    Joined:
    Jun 4, 2017
    Posts:
    575
    I'm wondering why you are calling something so often that is so slow.

    Just to check - is this a DLL where you don't have access to the code? Also does this DLL contain non-Unity code and is, therefore, safe to run on another thread?

    Could you not write a C# manager to run on it's own thread that would call the DLL regularly but offer a simple lightweight interface for your Unity code to call? Then it would act to decouple your components. If you later find a more efficient implementation of this DLL method, replacing it would be straightforward to do.
     
  8. HoloDeveleoper

    HoloDeveleoper

    Joined:
    May 14, 2018
    Posts:
    12
    @Doug_B your second Approach makes a lot of sense in my case. I am not sure if it is easy to do like that, since i have not just one Class, but a few that work together and rely on each other.
    but how i could imagine it would be that a Manager runs on its own Thread and everytime it is finished with a process, it invokes an Event on the main thread.

    Regarding the .dll, i constantly scan the enviroment, for that i have an OpenGL plugin attached that Scans that what i see.
     
  9. Doug_B

    Doug_B

    Joined:
    Jun 4, 2017
    Posts:
    575
    If these other classes are your own Unity ones, you could consider using a Dependency Injection approach on the DLL Manager's interface.

    That would work. Or have a shared, thread safe, memory area that the DLL Manager populates with latest results and then sets a flag to say the data is ready for reading. Your Unity code could then just poll the is-ready-flag.
     
  10. McDev02

    McDev02

    Joined:
    Nov 22, 2010
    Posts:
    540
    Maybe it is interesting, here is a script I used with switching threads in a coroutine. Maybe that can work too for oyur problem.
    Edit: Sorry In hindsight there is more in the background (which I didn't write myself). There is a whole Coroutine Threading manager which starts the coroutine. So I think this is all useless for you. I thought this was Unity default stuff.
    Code (CSharp):
    1.  
    2.         private IEnumerator FogOfWarProcess ()
    3.         {
    4.             while ( isRunning )
    5.             {
    6.                 yield return null; //Not sure why that is for
    7.                 if ( !IsVissible )
    8.                 {
    9.                     continue;
    10.                 }
    11.  
    12.                 yield return UnityCoroutine.Now;
    13.                 //Inside Unity Thread
    14.                 CollectData ();
    15.  
    16.                 m_FogMap.Apply ();
    17.                 m_FogOverlayMap.Apply ();
    18.  
    19.                 yield return ThreadCoroutine.Now;
    20.                 //Inside Other Thread
    21.  
    22.                 CalculateFogOfWar ();
    23.  
    24.                 System.Threading.Thread.Sleep ( THREAD_SLEEP_TIME );
    25.             }
    26.         }
    27.  
    28. //Other stuff
    29.     public sealed class UnityCoroutine
    30.     {
    31.         public static readonly UnityCoroutine Now = new UnityCoroutine ();
    32.  
    33.         private UnityCoroutine () { }
    34.     }
    35.  
    36.     public sealed class ThreadCoroutine
    37.     {
    38.         public static readonly ThreadCoroutine Now = new ThreadCoroutine ();
    39.  
    40.         private ThreadCoroutine () { }
    41.     }
    42.  
     
    Last edited: May 17, 2018
  11. HoloDeveleoper

    HoloDeveleoper

    Joined:
    May 14, 2018
    Posts:
    12
    I implemented it like this now, wich is without Coroutines and it is working like this. The Performance is not better a lot than before tho.

    Code (CSharp):
    1.   private staticTask task
    2. private void Start(){
    3. task = new Task(() => runner.DoSomeWork());
    4. }
    5.  
    6. private void LateUpdate() {
    7.         if (Time.time >= timeToGo || task.Status != TaskStatus.Running )
    8.         {
    9.             task.Start();
    10.             timeToGo = Time.time + 0.5f;
    11.         }
    12.     }
    Code (CSharp):
    1. private void DoSomeWork(){
    2. SomeExpensiveWork(ref Count, ref ids, ref vecs1, ref vecs2, ref vecs3);  //calls a .dll
    3. SomeOtherWork(Count, ids, vecs1, vecs2, vecs3)
    4. }
    My first guess would be that it is expensive to start a Task every time, wich would lead me to implementing it like @Doug_B suggested.
    if i comment out the LateUpdate method my app runns smoothly. So something there is still pretty expensive for the main thread it seems.
    The app runs on the HoloLens, maybe it has something to do with that?
     
  12. HoloDeveleoper

    HoloDeveleoper

    Joined:
    May 14, 2018
    Posts:
    12
    @McDev02 even tho it sounds easy i prefer not to swap between main thread and another thread inside a Coroutine.

    @Doug_B I dont really understand dependency injections, so im not sure if it is easy for me to do it that way
     
  13. oLDo

    oLDo

    Joined:
    Mar 14, 2017
    Posts:
    28
    About the main question of thread. I've few solutions where I'm combining threads and coroutines. Usually my approach is to start a thread with logic and the coroutine is waiting until the thread is finished.

    It make sense depends on problem and solution.
     
  14. Doug_B

    Doug_B

    Joined:
    Jun 4, 2017
    Posts:
    575
    That is very plausible. Just be aware that if you do run another dedicated thread, it will not be able to make Unity API calls directly.

    There is no need to use a full dependency injection framework. I was more thinking along the lines of having something like an init method :

    Code (CSharp):
    1. partial class CDLLManager
    2. {
    3.     public Init( ObjectINeedOne one, ObjectINeedTwo two )
    4.     {
    5.         m_ObjectOne = one;
    6.         m_ObjectTwo = two;
    7.     }
    8.  
    9.     ObjectINeedOne  m_ObjectOne;
    10.     ObjectINeedTwo  m_ObjectTwo;
    11. }
    This may help maintain a clean decoupled interface to make the DLL Manager easy to replace in the future if you so decided.
     
  15. HoloDeveleoper

    HoloDeveleoper

    Joined:
    May 14, 2018
    Posts:
    12
    Thanks a lot.
    I now have only one Task wich runs in the Background, instead of starting a new Task in Update.
    The app is pretty smooth right now. I have just a tiny bit lag when i try to capture the current view via WebCamTexture.Play and WebCamTexture.GetPixels32(). But These methods can only run on the Main thread as far as i know. So nothing to do here, but i am quite happy with how it is now.

    Thanks again guys.
     
    Doug_B likes this.
  16. Doug_B

    Doug_B

    Joined:
    Jun 4, 2017
    Posts:
    575
    Excellent news. Glad you got it sorted. :)
     
  17. oLDo

    oLDo

    Joined:
    Mar 14, 2017
    Posts:
    28
    I've solution where I'm processing image from other source as Texture. I'm doing this in VR and there is performance most important.

    My solution is using Microsoft.Drawing.Bitmap for reading pixel colors data and write it to buffer in background thread. Of course it's possible to use another library for reading picture pixel colors, depends on platform.
    Then standart MonoBehaviour Update() is reading this buffer and it's using it with Texture SetPixel(). After the background thread is finished main thread call Texture Apply().

    Works great and keep performance. Speed of loading picture depends on it's size, but usually take only few seconds.
     
  18. HoloDeveleoper

    HoloDeveleoper

    Joined:
    May 14, 2018
    Posts:
    12
    a few seconds is quite long in this case.
    i have a few ms delay at the Moment (10 ms or less) .
    U can Show me ur Code how it is done if u like to, i may try it, but right now i like how it is and Focus on other parts.
    Kind Regards
     
  19. oLDo

    oLDo

    Joined:
    Mar 14, 2017
    Posts:
    28
    I need to say, we are usually opening pictures of size few MB. Sometimes bigger, like 20MB. If you can open big picture as texture in 10ms I'm more interest about your solution. :D

    Sorry I can't share this code. It's company property.
     
  20. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    5,571
    The WWW class supports loading local files and supports asynchronous loading.
    https://docs.unity3d.com/ScriptReference/WWW.html

    Another option is to just load the image with the System.IO namespace, and then create a Texture2D from a byte array formed from that using Texture.LoadRawTextureData:
    https://docs.unity3d.com/ScriptReference/Texture2D.LoadRawTextureData.html
     
  21. oLDo

    oLDo

    Joined:
    Mar 14, 2017
    Posts:
    28
    When I was developing this solution I tried both this options. Isn't good in VR if you need to keep performance.
    WWW is asynchonous loading, but after load it is creating Texture in main thread in one frame. It freeze the scene for a short moment.
     
  22. HoloDeveleoper

    HoloDeveleoper

    Joined:
    May 14, 2018
    Posts:
    12
    I dont think my solution would work for u, i have no Pictures that i load, i just get the current Frame that the Main Camera sees, and do some work with it.
     
    oLDo likes this.