- Home /
Iteration of coroutines
What I'm really looking for is some way to simulate this:
IEnumerator Start(){
for (int i = 0; i < endValue; i++){
StartCoroutine(_Coroutine(i));
}
yield return [Wait until for loop has finished, before resuming];
//Do more things
}
IEnumerator _Coroutine(int i){
//Do things here with the only variable being i
}
At the moment, the only work around I can think of is:
for (int i = 0; i < endValue; i++){
yield return StartCoroutine(Coroutine(i));
}
Obviously that's not ideal as each iteration would wait for the previous one to complete, which (in my case) is unnecessary.
Could anyone shed some light on how to go about the problem? Cheers :D
Edit to explain purpose:
I'm using the Google Play Unity plugin and have multiple leaderboards. I want to fetch the data from all the leaderboards, but prevent the user from playing until all scores are fetched. This is what I currently have:
IEnumerator GetHighscore(int i) {
bool gettingScore = false;
if (Social.localUser.authenticated) {
gettingScore = true;
PlayGamesPlatform.Instance.LoadScores(
GPGSIds.leaderboard[i], //Fetch scores from this leaderboard
LeaderboardStart.PlayerCentered, //Start from player position
1, //Get n results from position
LeaderboardCollection.Social, //Get the results from social/public
LeaderboardTimeSpan.AllTime, //No time length
(data) => {
//Callback
Global.highscore2[i] = int.Parse(data.PlayerScore.formattedValue);
gettingScore = false;
}
);
}
yield return new WaitWhile(() => gettingScore);
}
I don't know if it's a solution or a workaround, but here's what i'd do:
Before starting the coroutines, init a counter with 0.
Each coroutine must increase this counter by 1 before it ter$$anonymous$$ates.
Periodically check if the counter has reached its final value (= number of coroutines)
That's called a Semaphore. It's the 100% standard way of waiting until several people are finished, as it appears in any Operating Systems textbook.
Well, except you usually count down -- add 1 when you start, subtract 1 when finished. That way the counter is how many people are still working, with a nice 0 meaning all finished.
Thanks, i even know what a Barrier class is ;)
$$anonymous$$y selfdoubts were more about whether there might be a Unity Coroutine "trick" that does the same.
Answer by Bunny83 · Sep 13, 2016 at 08:45 PM
That's a quite unusual case. Keep in mind that coroutines are cooperative routines which will execute one after another. Using coroutines here only makes sense if your "Coroutine" actually does something across multiple frames.
Starting say 100 of your Coroutines in one go means each of those coroutine will perform "one step" each frame. One step is everything from a yield to the next yield. So if you do heavy stuff inside the coroutines and you run a lot at the same time it will still cause performance issues. Coroutines can not be effectively used as load balancers. That's not what they are for. They just allow several such routines to seemingly run in parallel but without any time control. It's pure cooperative. If one routing doesn't yield for 10 seconds your whole application will be freezed during that time.
Btw: It's a bad idea to name your coroutine Coroutine as that's the name of the Coroutine class in Unity. So you could get into name collision problems.
Anyways, if you want to start several coroutines at the same time and want to wait until all have finished you can store them into an array / list like this:
IEnumerator Start(){
Coroutine[] coroutines = new Coroutine[endValue];
for (int i = 0; i < endValue; i++){
coroutines[i] = StartCoroutine(_Coroutine(i));
}
for (int i = 0; i < endValue; i++)
{
yield return coroutines[i];
}
Debug.Log("All coroutines have finished");
}
IEnumerator _Coroutine(int i){
//Do things here with the only variable being i
}
Note that it's important that you can only yield once on a Coroutine object. Never try to pass the same coroutine object two times to yield return, no matter if in the same coroutine or different ones. This will mess up the internal coroutine system and your coroutine can never finish and will wait infinitely. At least that's how it used to be.
When you yield return a coroutine object of a coroutine that has already finished the yield will immediately return. This is pretty much the only case where a coroutine can be resumed multiple times per frame (ignoring special cases like waiting for fixedupdate or first yielding null and then WaitForEndOfFrame).
So when you start 100 coroutines and each will finish at a different frame, even when all except the first one have already finished, once the first one has finished your "controlling" coroutine will advance. Since each coroutine that has already finished will immediately return it will rush through the array until it hits a coroutine that hasn't yet finished. At the time all coroutines have completed the second for loop will be done as well.
edit
As mentioned in my comment above, if you don't mind to add some code to the coroutines you run, you can use an instance counter variable like this:
int instances = 0;
IEnumerator Start(){
instances = 0;
for (int i = 0; i < endValue; i++){
StartCoroutine(_Coroutine(i));
}
while (instances > 0)
yield return null;
}
IEnumerator _Coroutine(int i){
instances++;
//Do things here with the only variable being i
instances--;
}
Note: This is kind of dangerous. If a coroutine is terminated without executing the decrement at the end (due to an exception or StopCoroutine) your "Start" coroutine will be stuck as the variable will never return to 0.
Right. That edit is what double$$anonymous$$ax suggested (and I mentioned usually counts down, as you have it.) I usually add to instances inside the loop, since I don't trust the coroutine to start right away.
A trick with this is you don't need to yield on the count. You could also just check if(instances
Also yes, I don't see this as an efficient design trick, but sometimes you just naturally want to run several coroutines. $$anonymous$$y last time was making several WWW requests and waiting until they all finished.
Well your answer fitted my situation, however you said it's an unusual case, so it's more than likely I'm just incorrectly using coroutines for something that doesn't require it (it's just it was a solution that worked and I probably dug myself deeper into the rabbit hole).
I've edited the question for your interest. Just for my own learning, is there any better approach you would have taken?
Your answer
Follow this Question
Related Questions
How to slow down large code? 1 Answer
Play animation 5 sec, stop for 5 seconds, repeat 0 Answers
Simplify this code 3 Answers
Using a while loop in coroutine as a contained Update function 1 Answer
Why is this coroutine only firing once? 3 Answers