- Home /
Return value from coroutine to non monobehaviour
Hey,
I have a class which is not a monobehaviour (lets call it "Generation"), and as such running CoRoutines from within are not an option.
I have a monobehaviour (lets call this "TestScript") which needs to call a function within Generation. One parameter required is a Func>. It then calls that Func multiple times, expecting a result in the form of a double. The problem, is that it takes time to return, it needs to run across many frames, and so it kicks off some Coroutines (as this is in the monobehaviour). I set it to return a Task so that I could use Threading.SpinWait.SpinUntil and only return after the coroutines have exited, but this just hangs the main Unity thread.
Here is the function I am calling:
public void AssessGeneration(Func<NeuralNet, Task<double>> assessment)
{
foreach (NeuralNet neuralNet in neuralNets)
{
neuralNet.Fitness = assessment(neuralNet).Result;
neuralNet.fitnessAssessed = true;
}
}
So, I've tried many other things, such as moving the SpinWait into the Generation class, but still hangs. I've tried calling the function in Generation within a Task on a new thread, but when it attempts to run the coroutines it complains about not being on the main thread.
So - I have a function, in a class not inheriting from MonoBehaviour. It is called from a MonoBehaviour, and must pass in a function which takes a parameter of type NeuralNet, and returns a double. It must return a double after a coroutine has finished, but I can't be blocking the main thread.
Answer by Bunny83 · Mar 12, 2018 at 11:28 AM
If you call SpinWait you basically remove all advantages of using threads as you will make the tread that calls SpinWait to wait at the point until the thread / task is completed. You may want to check the IsCompleted property of the Task class inside a coroutine like this:
while (!task.IsCompleted)
yield return null;
This will keep the coroutine running while checking every frame if your task is completed.
Keep in mind that Unity's scripting environment runs on a single thread only, the main thread. This includes coroutines. Also keep in mind that coroutines are not normal "methods" but generator methods. They return an instance of a statemachine object. This statemachine actually represents the body of your coroutine. This statemachine, one passed to StartCoroutine, is driven by Unity's coroutine scheduler on the main thread. So when you block any piece of code inside a coroutine the whole main thread will block. Coroutines are not threads. They implement cooperative multitasking. If you do not cooperate the whole application breaks ^^. When you do a yield return
you basically give the control back to Unity.
Some useful info, thanks. But it still doesnt solve that I have a non $$anonymous$$onobehaviour that requires a value to be returned from a $$anonymous$$onobehavious running a coroutine for a length of time.
If I understand your setup correctly, the above does solve your situation: when you call the function on "Generation" that should give you a double
, you return a Task<double>
, and then wait using yield return null
while task.Iscompleted
is false.
If it is the other way around (as your comment suggests), that it is the non-$$anonymous$$onoBehaviour "Generation" who is waiting on the coroutines, then your solution is to use the $$anonymous$$onoBehaviours like tasks, and put a bool isCompleted variable into them that "Generation" reads in a separate thread. However, I do not recommend this approach, it feels backwards and prone to typical threading issues.
Then you haven't presented your problem in a way we can actually follow it. Of course once the asyncronous task is completed you can simply read the result like this:
// inside your coroutine
while (!task.IsCompleted)
yield return null;
double val = task.Result;
However if you actually want the opposite you need to use a WaitHandle or something like that which makes the background thread to wait for a signal raised on the main thread. One way is to use my extension of the Loom class called LoomEx. You can use "QueueOn$$anonymous$$ainThread" to schedule a delegate on the main thread. Note that it is important to initialize the LoomEx class on the main thread. Usually inside Awake of a $$anonymous$$onoBehaviour by calling LoomEx.Initialize();
.
$$anonymous$$y extended version of the Loom class has an option to wait for the main thread to process the queued item. So inside a background thread you can do something like:
// inside background thread
double someValue = 0;
LoomEx.QueueOn$$anonymous$$ainThread(() => {
// this is executed on the main thread
someValue = some$$anonymous$$ethodThatHasToRunOnThe$$anonymous$$ainThread();
}, 0, true);
// use "someValue" in the background thread.
However if you need a more specific solution for your specific case you should provide more context of your exact setup, who is calling / executing which method / coroutine and what data should be transferred from which context to what other.
Your answer
Follow this Question
Related Questions
Does UnityEngine.CustomeYieldInstruction works in a seperate thread? 2 Answers
What does new Thread do, and how many threads are too many? 1 Answer
will monobehaviour stay alive after DOTS? 0 Answers
Job System without using the main thread 1 Answer
What's the best way to structure a recursive long running algorithm? 1 Answer