- Home /
Can't get while loop to execute more than once in a coroutine before yielding [Solved]
This has been driving me crazy. If anyone has insight in why it's behaving like this, I'd love to hear it.
So, I have a while loop inside a coroutine. It works fine. However, yielding each frame is too slow for my purposes. So I'm trying to make it yield after every 4 loops. In my test case I want the while to execute a total of 1000 times (yielding 250 times along the way). It always freezes unity after around 400 total loops. Help?
private void CoroutineFunction()
{
int loop = 0;
//start = 0; end = 1000;
while (start != end)
{
start++;
//some instantiation code omited here
if (loop % 4 == 0)
{
yield return null;
}
loop++;
}
}
To be clear - if I remove the loop
variable and the if %
check, and just yield every frame it works. But why would my changes make unity freeze? How would they cause the while loop to get stuck? How else could I implement yielding every x loops?
Answer by Bunny83 · Aug 25, 2018 at 11:28 AM
It might depend on your "some instantiation code omited here". The code provided should work fine. Maybe each instantiated object also starts this or a similar coroutine? Since there's no yield between the start and you instantiate code it runs synchronously. Though if you have an infinite recursion you should get a stack overflow and not a freeze (though if the instantiate code is quite heavy it might take a while).
If you just want a certain call count per second you could use a custom fixed update callback. This works similar to Unity's FixedUpdate but you can specify any desired call rate, even 10000 times per sec. Though it does not perform load balancing. So you should ensure the cost of a single call is not higher than the deltatime between two fixed calls. So when doing 10000 calls each call should not take longer than 0.0001 seconds (0.1ms). Keep in mind that at a visual framerate of 60 fps there would be about 166 calls each frame.
The CustomFixedUpdate does have a "MaxAllowedTimeStep" (like Unity has) to not get caught in a death spiral.
Thanks for trying to help me figure this out!
I'm happy to hear you too think that the code should work as is.
This is the only coroutine I have and there is no recursion.
The missing part of the code calls FindObjectsOfType(); for two types of objects, foreach-es through them and calls a function for each of them. The function looks at some other parameters of the object and decides if it should Destroy() itself or Instantiate() more copies of the object. That's about it. As the while loop in the coroutine continues, there tends to be more and more objects found by the Find call, but it's nothing unity can't handle. The Update() method for each of those objects immediately "returns" if they detect the coroutine is running. So there is no recursion.
It's not that I want a certain call count per second. This code runs perfectly well synchronously, but it freezes for the few seconds while it does the calculations. That is why I offloaded it onto a coroutine. And as I said, if I yield every while loop, it crunches all the data fine, no problems.
Any other ideas what I should check or try?
Well as i said i don't see any reason why this loop should freeze. If "start" is actually incremented reliably by 1 each time you could get rid of your loop variable all together and just do
if (start % 5 == 0)
{
yield return null;
}
This should yield once every 5 iterations.
A bit unrelated but the way you setup your loop variable results in some inconsistent behaviour. You set "loop" to 1. So it does 4 iterations and then enter your if statement. However since you increment loop at the end you effectively setting loop to "2". So from now on you only yield every third iteration. Resetting loop is not really necessary when you use modulo. If you reset the variable each time it would be much more readable to just use
loop++;
if (loop > 4)
{
loop = 0;
yield return null;
}
Good point on the reset. It's because I've gone through many iterations of trying different things, so that's a bit of a jumble. You're perfectly correct on the the modulo thing. It's not the problem though.
However, I found a lead on what might be the cause though! It seems if you call Destroy() on an object, it only gets destroyed on the next Update loop, but before rendering (so its still the same frame). But now, I do 4 (or well.. 3) loops, before yielding. If the first loop ends up calling Destroy(), then the other 3(2) loops will also call Destroy() on the same object. I'm not sure how Unity works that far under the hood, but I think this might cause a problem.
Looking into a way to check if Destroy() has been called on an object yet. Worst case scenario, I can use an internal variable. I'll update you if anything comes of this.
Answer by Em3rgency · Aug 26, 2018 at 03:50 PM
So the issue was related to the omitted Instantiation code. In hindsight the problem is obvious.
The objects I was instantiating were running some internal code as part of the coroutine that instantiated them. And several of them had while() loops of their own, that in my situation had become infinite loops. The reason was that they did not get properly initialized as their Start() functions were not called until next frame (by which time it was already too late).
The solution was simply to use Awake() instead of Start() on all objects instantiated on the coroutine. This ensures they are properly initialized, even if the next frame hasn't started yet.