Search Unity

Accessing a GameObject component within a thread?

Discussion in 'Scripting' started by Eoghan, Feb 28, 2015.

  1. Eoghan

    Eoghan

    Joined:
    Jun 6, 2013
    Posts:
    80
    Hey Unity peoples :) I'm working on a project at the moment, in which I've implemented my own networking controls via the .NET Sockets library. Everything is working great so far when dealing with internal methods - but I've hit a brick wall when I attempt to call GetComponent on a GameObject from within my networking threads.

    As a quick example, I set up a script that scans for any local servers running; and set the scan to stop once it finds the first game server running. So to stop this scan, a udp receiver object runs;

    Code (csharp):
    1. networkScan.GetComponent<NetworkScan>().foundServer = true;
    This then returns an error, stating;

    GetComponent can only be called from the main thread.

    Constructors and field initializers will be executed from the loading thread when loading a scene.

    Don't use this function in the constructor or field initializers, instead move initialization code to the Awake or Start function.


    I understand what the error is saying - that basically, when threaded, this script seems to lose the ability to call methods within components that are running in the main Unity thread.

    I'm hoping there's a way I can get around this - it'd be a killer if I couldn't interact with GameObjects in my scene, on account of me running a thread for receiving UDP packets.

    Any help would be hugely appreciated! :)
     
  2. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,531
    How I usually deal with threading is that I have an 'AsyncOperation' object that can be used as a yield statement in a coroutine.

    This thing blocks until the AsyncOperation has been flagged as completed by the alternate thread. It also houses any return values.

    Then, in a coroutine, I spin up the thread, the thread does its thing. I yield the asyncoperation. Then when the thread is done doing its thing, it signals the asyncoperation, and then the coroutine continues operating on the main thread.
     
    Kiwasi likes this.
  3. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    Unity handles thread safety by basically restricting access to its API to the main thread.

    You can get around this with any of the standard threading techniques. Pass the references you need when you create a thread. Queue the GetComponent requests on the main thread. Or use a shared data structure that both threads can manipulate.
     
  4. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,531
    Here's an example. In it I use the RadicalAsyncOperation from my 'Spacepuppy Framework' found here:
    https://code.google.com/p/spacepupp...SpacepuppyBase/Async/RadicalAsyncOperation.cs


    Code (csharp):
    1.  
    2. void Start()
    3. {
    4.     this.StartCoroutine(this.Routine());
    5. }
    6.  
    7. IEnumerator Routine()
    8. {
    9.     var op = new CustomAsyncOperation();
    10.     op.Begin(); //this starts the thread, it will automatically call 'DoAsyncWork', see linked source
    11.     yield return this.StartCoroutine(op);
    12.     Debug.Log(op.ReturnValue);
    13. }
    14.  
    15. public class CustomAsyncOperation : com.spacepuppy.Async.RadicalAsyncOperation
    16. {
    17.     public string ReturnValue;
    18.  
    19.     protected override void DoAsyncWork()
    20.     {
    21.         //do async stuff, when complete signal complete
    22.         this.ReturnValue = "BLARGHA BLARGH";
    23.         this.SetSignal();
    24.     }
    25. }
    26.  
    You may notice I implement some interfaces that are unfamiliar in the RadicalAsyncOperation. This is because it supports my 'RadicalCoroutine' system which is an expanded version of the built in Coroutines in unity. Really though, it just implement IEnumerator, so to use it just do that.
     
    Last edited: Feb 28, 2015
  5. Eoghan

    Eoghan

    Joined:
    Jun 6, 2013
    Posts:
    80
    Can you tell me how I'd be able to queue the GetComponent requests on the main thread?

    @lordofduct ; this looks like an interesting solution, but I don't think I can actually use it in my particular project - the udp send/receive threads would need to be always running, so they'd never really yield at any point in running.
     
  6. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    Create a MonoBehaviour as an entry point for threads. Give that MonoBehaviour a Queue of tasks. Each task contains some code to execute on the Unity API, a place for a return value, and a flag to indicate it's finished.

    Your threads create the tasks and adds them to the queue. It can check the flag to find out when the task is done, then take action based on the returned data.

    The MonoBehaviour executes the tasks during its Update function. Once each task is complete and provided with a return value the task is removed from the queue and flagged complete.

    Sound complex enough? Welcome to multi threading. Note that @lordofduct proposed essentially the same system. But the waiting is on the MonoBehaviour side, and the task is on the thread side.
     
    lordofduct likes this.
  7. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,531
    In that case have an invoke list.

    Basically have some global singleton out there that can take in requests to perform tasks on the main thread. You pass in a delegate that will get called on the next frame. This allows you to callback to that main thread if necessary.

    Again, back in my spacepuppy framework, I have a class called 'GameLoopEntry'. This is a Singleton (note it inherits from my Singleton class as well) that hooks anything into the main thread.

    https://code.google.com/p/spacepupp.../browse/trunk/SpacepuppyBase/GameLoopEntry.cs

    On it I have a function called 'InvokeNextUpdate'. This stores an Action to be called the next time Update ticks on the GameLoopEntry.

    In your async routine you can invoke this.

    Code (csharp):
    1.  
    2. void AsyncJob()
    3. {
    4.     //doing stuff
    5.  
    6.     //reached point where callback necessary
    7.     GameLoopEntry.InvokeNextUpdate(() => {
    8.         networkScan.GetComponent<NetworkScan>().foundServer = true;
    9.     });
    10.  
    11.     //continue on doing work.
    12. }
    13.  
    You can even have an async wait handle to block with until its done.

    Code (csharp):
    1.  
    2. void AsyncJob()
    3. {
    4.     //doing stuff
    5.  
    6.     //reached point where callback necessary
    7.     var handle = new EventWaitHandle(false, EventResetMode.AutoReset);
    8.  
    9.     GameLoopEntry.InvokeNextUpdate(() => {
    10.         networkScan.GetComponent<NetworkScan>().foundServer = true;
    11.         handle.Set();
    12.     });
    13.  
    14.     //wait until the invoke signals the handle
    15.     handle.WaitOne(); //CAUTION - a call to this on the main thread can freeze the game, only call in a separate thread
    16.  
    17.     //continue on doing work.
    18. }
    19.  
     
    Kiwasi likes this.
  8. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,531
    Note, if you like these solutions. Check out the rest of my Spacepuppy Framework. What's on the google code page is the open-source version of it (it's a slimmed down version of the over-arching framework I've been working on for a 6 months now).

    I add to it constantly. This week I added my new 'Scene' system that creates object representations for scenes, and gives an entry point into a scene on load that is more manageable than a random GameObject plopped into each and every scene.
     
  9. Eoghan

    Eoghan

    Joined:
    Jun 6, 2013
    Posts:
    80
    I actually picked up the Loom Framework after doing a bit of digging around, and it seems like an awesome solution - I've got separate threads running for handling udp data, and it's able to communicate back to the main Unity3d thread without issue.

    Unfortunately, one step forward, and another back.. it appears there's now no way for me to abort the thread in the OnApplicationQuit method, causing Unity3D to crash out whenever I close the project. Would anyone have any ideas on how I could solve this final piece of the puzzle?
     
  10. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,531
    Have a thread pool from which you draw all threads.

    Then in some master game loop script (like my GameLoopEntry), have a callback for the OnApplicationQuit and cancel all the threads in that thread pool.

    I haven't used Loom, but I'd assume they'd have something for that. If it's a multi-threading solution for unity.
     
  11. Eoghan

    Eoghan

    Joined:
    Jun 6, 2013
    Posts:
    80
    Oddly enough, it seems to rely on an internal tool to automatically close threads.. which is strange to say the least. For whatever reason, UDP Sockets seem to refuse to close in Unity3D (calling shutdown/close works fine - but the socket seems to just remain open. A quick google search says it's a bug related to the adb background process); so I just called Thread.abort on it previously.

    Looks like I may have to contact their support team to get around this, unless anyone on here might have experience with it.