Search Unity

  1. Good news ✨ We have more Unite Now videos available for you to watch on-demand! Come check them out and ask our experts any questions!
    Dismiss Notice
  2. Ever participated in one our Game Jams? Want pointers on your project? Our Evangelists will be available on Friday to give feedback. Come share your games with us!
    Dismiss Notice

How to Synchronize Unity thread with other threads

Discussion in 'Scripting' started by zlSimon, Oct 13, 2015.

  1. zlSimon

    zlSimon

    Joined:
    Apr 11, 2013
    Posts:
    31
    I still having trouble getting my head around this threading issue.
    I have a bunch of Webrequest which should run on different threads. So my idea was to have a Singleton which holds a dictionary of Tasks. Every access to the dictionary is surrounded by lock to have it thread safe. When a webrequest is started from the main thread only a Task is being created an put in the dictionary.
    The Task contains all necessary information to start a webrequest including a callback with the result.
    A coroutine is running and checking if there is a Task in the dictionary which needs to be started, if so a new thread is started with the webrequest, when the webrequest is done the corresponding task flag is set and the result data is stored in the task. When the coroutine encounters a finished task, the callback is being called with the data.

    Will that work? And if so, are there any drawbacks I have to take care?
     
  2. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    7,153
    1) this expects all threads that may need access to the main thread to have to be started with the intention of doing so from the get go.

    2) this concludes that all completed tasks are successful and calls the callback regardless

    Why have the coroutine start the task? Why not just have some singleton that allows a call to invoke on it. Something as simple as this:

    Code (csharp):
    1.  
    2. public class InvokePump : MonoBehaviour
    3. {
    4.  
    5.     #region Singleton Interface
    6.     //implement this however you please, I personally do this different, this is here for example only
    7.     private static InvokePump _instance;
    8.     public static InvokePump Instance
    9.     {
    10.         get
    11.         {
    12.             return _instance;
    13.         }
    14.     }
    15.  
    16.     void Awake()
    17.     {
    18.         _instance = this;
    19.     }
    20.     #endregion
    21.  
    22.     private object _lock = new object();
    23.     private System.Action _callbacks;
    24.  
    25.     public void Invoke(System.Action callback)
    26.     {
    27.         lock(_lock)
    28.         {
    29.             _callbacks += callback;
    30.         }
    31.     }
    32.  
    33.     private void Update()
    34.     {
    35.         //sure, this updates constantly, again, for demo purposes... implement the actual update hook however you please
    36.      
    37.      
    38.         //here we pull the delegates out, incase the callback calls 'Invoke'
    39.         //if Invoke is called, the lock would create a deadlock, and freeze the game
    40.         System.Action a;
    41.         lock(_lock)
    42.         {
    43.             if(_callbacks != null)
    44.             {
    45.                 var a = _callbacks;
    46.                 _callbacks = null;
    47.             }
    48.         }
    49.         if(a != null) a();
    50.     }
    51.  
    52. }
    53.  
    Then in your code you can just hook back at the end of the thread by saying:

    Code (csharp):
    1.  
    2. void DoMultiThreadWork()
    3. {
    4.     //in thread... do stuff with webrequest
    5.  
    6.     InvokePump.Instance.Invoke(() =>
    7.     {
    8.        //do whatever you do on the main thread
    9.     });
    10. }
    11.  
    I have a more robust version that I do with this class called 'InvokePump':
    https://github.com/lordofduct/space...lob/master/SpacepuppyBase/Async/InvokePump.cs

    Note it's not a Singleton, but instead I can create different kinds of pumps in a Singleton, so that way I can return to FixedUpdate, or regular Update, or whatever point in code I want the InvokePump to work on. As you can see here:
    https://github.com/lordofduct/spacepuppy-unity-framework/blob/master/SpacepuppyBase/GameLoopEntry.cs
     
    Last edited: Oct 13, 2015
  3. zlSimon

    zlSimon

    Joined:
    Apr 11, 2013
    Posts:
    31
    So if I understood you correctly, the method DoMultiThreadWork() runs in a new thread and the thread is being started from unity?
     
  4. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    7,153
    yeah, that method would be called from wherever in your game. Could be from a thread started by a thread, it can start anywhere.

    More explicitly:

    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4. using System.Threading;
    5.  
    6.  
    7. public class zTestScript : MonoBehaviour {
    8.  
    9.  
    10.     private Thread _thread;
    11.  
    12.     void Start()
    13.     {
    14.  
    15.         _thread = new Thread(this.DoMultiThreadWork);
    16.         _thread.Start();
    17.     }
    18.  
    19.  
    20.     private void DoMultiThreadWork()
    21.     {
    22.         System.Threading.Thread.Sleep(1000);
    23.  
    24.         InvokePump.Instance.Invoke(() =>
    25.         {
    26.            //do whatever you do on the main thread
    27.         });
    28.  
    29.         _thread = null;
    30.     }
    31.  
    32. }
    33.  
     
  5. zlSimon

    zlSimon

    Joined:
    Apr 11, 2013
    Posts:
    31
    ok that is actually quite nice but I would need a callback set with custom data from outside, but that should also work right? Something like that:
    Code (CSharp):
    1.  private void DoMultiThreadWork(Action<SomeData> actualCallback)
    2.     {
    3.         System.Threading.Thread.Sleep(1000);
    4.         InvokePump.Instance.Invoke(() =>
    5.         {
    6.           actualCallback(new SomeData());
    7.         });
    8.         _thread = null;
    9.     }
    Another approach would be changing the callback from the InvokePump to something like Action<WebResonseBase> and then cast the resonse at the end to the desired class.

    Another small question, is it necessary to null the _thread at the end?
     
  6. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    7,153
    Do it like the code you posted. That way 'Invoke' is generic. It can take in any ambiguous delegate of shape 'Action', and it can handle the calling of an oddly shaped callback.

    Otherwise InvokePump would have to have overrides for all possible callbacks you want. Defeating the point of a generalized shape for the InvokePump.

    Sure it creates a small bit of garbage for GC, but anonymous functions are VERY powerful, and as long as they're used to a reasonable amount, you get more freedom.

    And of course, because you'd have to store those parameters in some data structure to support callback in the next Update call... that would in turn create garbage of almost equal size to the anonymous function. The anonymous function just creates the state data structure for you. So in the end... it's less code, more generalized, and causes nearly the same amount of memory cost as would be with explicit callback shapes.
     
unityunity