Search Unity

Dispatching functions to the main thread from other threads in Unity

Discussion in 'Scripting' started by pdwitte, Apr 2, 2016.

  1. pdwitte

    pdwitte

    Joined:
    Feb 3, 2016
    Posts:
    17
    Hey all, I need your feedback on something!

    So I ran into a problem this week where Unity did not allow for a simple way of executing UI functions from different threads. It blocks the modification of scene-related objects from other threads. This is quite annoying, especially if you're working with certain multithreaded networking libraries that dispatch event handlers to different threads.

    I made a quick fix for it that is easy to use and noob-friendly. It uses coroutines that are queued in a thread-safe way and executed on the main thread through a game object that is added to the scene.

    Can you guys let me know if this is useful at all, or if I should be doing it another way?

    You can use it like this:
    Code (CSharp):
    1. public IEnumerator ThisWillBeExecutedOnTheMainThread() {
    2.     Debug.Log ("This is executed from the main thread");
    3.     yield return null;
    4. }
    5. public void ExampleMainThreadCall() {
    6.     UnityMainThreadDispatcher.Instance().Enqueue(ThisWillBeExecutedOnTheMainThread());
    7. }
    Simply head over to https://github.com/PimDeWitte/UnityMainThreadDispatcher and start using it if you'd like.
     
  2. Jamster

    Jamster

    Joined:
    Apr 28, 2012
    Posts:
    1,102
    I recently had to write similar for the new version of my DarkRift Networking asset, it's interesting to see your version :)

    Looks good, mostly thread safe but you should have a lock in your update routine as well because you could end up enqueuing and dequeuing at the same time which could cause you problems irregulally. I would also suggest using the built in thread safe collections in System.Collections.Concurrent if you can, it just ensures it's 100% safe and implemented in the best way possible :)

    Good work!

    Jamie
     
  3. hippocoder

    hippocoder

    Digital Ape

    Joined:
    Apr 11, 2010
    Posts:
    29,723
    use correct forum.
     
  4. zoran404

    zoran404

    Joined:
    Jan 11, 2015
    Posts:
    520
    He does it to promote the repository, it would probably be lost in the scripting sub-forum with all the questions.

    Also I don't like the implementation, you can't use lambda-s or normal functions, but only ienumerators.
     
    Last edited: Apr 2, 2016
    Fattie likes this.
  5. hippocoder

    hippocoder

    Digital Ape

    Joined:
    Apr 11, 2010
    Posts:
    29,723
    It is a scripting question. If he wants to promote it, it must be done in the correct forum, ie showcase or wip. We had reports for this thread and acted accordingly, personal feelings don't enter into it.
     
  6. pdwitte

    pdwitte

    Joined:
    Feb 3, 2016
    Posts:
    17
    Truthfully it was not to promote the repository. I have about 3 months in Unity and recently started after about 7 years of Java development. I'm committing this code and wanted to make sure it was the right way to do it before telling other people it is.
     
    Fattie likes this.
  7. pdwitte

    pdwitte

    Joined:
    Feb 3, 2016
    Posts:
    17
    Apologies for not using the correct forum! Pretty new as you can see, will be more careful next time.
     
  8. pdwitte

    pdwitte

    Joined:
    Feb 3, 2016
    Posts:
    17
    Thanks dude. Appreciate it, and good point on dequeuing. You're totally right. That could happen, made the change: https://github.com/PimDeWitte/Unity...mmit/17d5d0c3214e1adad0ac500adda89d4bc8b00b35
     
    Fattie likes this.
  9. lp35

    lp35

    Joined:
    Apr 25, 2017
    Posts:
    1
    Hi,

    Your script is very simple and straigthforward to understand. One remark, I really miss an EnqueueAndBlock function...
     
  10. zoran404

    zoran404

    Joined:
    Jan 11, 2015
    Posts:
    520
    You could use callbacks to accomplish whatever you need.
     
  11. 5argon

    5argon

    Joined:
    Jun 10, 2013
    Posts:
    1,555
    Hello. Found this thread while working on similar code called `CoroutineHost`. I took the hint of using lock to my own code from this thread.

    It is almost the same, but it does not require placing a game object beforehand. In exchange, if you want to do it cross thread you have to call `CoroutineHost.PrepareCrossThread()` to instantiate it manually.

    Also it supports modern C# like `Task` if you are using .NET 4.6 of Unity 2017. You can `await` for things in the main thread from your child thread with this.

    https://github.com/5argon/E7Unity/tree/master/CoroutineHost
     
    wujinjindx and Jamster like this.
  12. 5argon

    5argon

    Joined:
    Jun 10, 2013
    Posts:
    1,555
    Wow I almost forgot this. Nowadays I am using UniRx.Async instead. What it did is utilizing the experimental PlayerLoop API and add some custom callback points into it. This way it doesn't even have to rely on any game object and also I can make it work with C# async/await. await will turns into frame-waiting await just like how yield enumerate waiting works, but with cleaner code and return values.

    https://github.com/neuecc/UniRx#unirxasync
     
    hippocoder likes this.
  13. Fattie

    Fattie

    Joined:
    Jul 5, 2012
    Posts:
    476
    Thanks for that fascinating tip, @5argon !! Cheers
     
  14. craig4android

    craig4android

    Joined:
    May 8, 2019
    Posts:
    124
    what about:

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class MainThreadDispatcher : MonoBehaviour
    6. {
    7.     public static MainThreadDispatcher current;
    8.     public List<System.Action> actions;
    9.     private void Awake()
    10.     {
    11.         current = this;
    12.         actions = new List<System.Action>();
    13.     }
    14.     public static void dispatch(System.Action action)
    15.     {
    16.         MainThreadDispatcher.current.actions.Add(action);
    17.     }
    18.     // Update is called once per frame
    19.     void Update()
    20.     {
    21.         List<System.Action> old = actions;
    22.         actions = new List<System.Action>();
    23.         foreach (System.Action action in old)
    24.         {
    25.             action();
    26.         }
    27.     }
    28.     private void OnDestroy()
    29.     {
    30.         if (current == this) current = null;
    31.     }
    32.  
    33. }
     
  15. Fattie

    Fattie

    Joined:
    Jul 5, 2012
    Posts:
    476
    @craig4android , it's great code in most settings, but the thing is in Unity you just really don't use threads in that paradigm. Unity is frame based! So it's completely different.

    All you do is drop values for UX elements to collect.

    "In a frame-based system, any game items should simply be displaying or behaving based on some "current value," which is set somewhere. If you have info arriving from other threads, just set those values - done."

    Here's a long discussion and indeed heated argument about it on SO ! https://stackoverflow.com/a/54184457/294884

     
  16. craig4android

    craig4android

    Joined:
    May 8, 2019
    Posts:
    124
    actually i improved it: Just use blocking collection:

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Concurrent;
    3. using System.Collections.Generic;
    4. using UnityEngine;
    5.  
    6. public class MainThreadDispatcher : MonoBehaviour
    7. {
    8.     BlockingCollection<System.Action> blockingCollection;
    9.     public static MainThreadDispatcher current;
    10.     private void Awake()
    11.     {
    12.         current = this;
    13.         blockingCollection = new BlockingCollection<System.Action>();
    14.     }
    15.     public static void dispatch(System.Action action)
    16.     {
    17.         MainThreadDispatcher.current.blockingCollection.Add(action);
    18.     }
    19.     // Update is called once per frame
    20.     void Update()
    21.     {
    22.         System.Action action;
    23.         while (blockingCollection.TryTake(out action)) action();
    24.     }
    25.     private void OnDestroy()
    26.     {
    27.         if (current == this) current = null;
    28.         blockingCollection.Dispose();
    29.     }
    30.  
    31. }
     
    Armynator likes this.
  17. Fattie

    Fattie

    Joined:
    Jul 5, 2012
    Posts:
    476
    Right but it doesn't actually work Craig ! :)

    It's just not how you handle a frame based system. All you do, all you can do, is drop values for a component to display or use. That frame. It's really that easy.

    https://stackoverflow.com/a/54184457/294884
     
  18. craig4android

    craig4android

    Joined:
    May 8, 2019
    Posts:
    124
    sure you won't use it normally but there is no alternative for instance in netcode