I wish to have a coroutine that starts several other coroutines that will run simultaneously and then waits for all of those coroutines to finish. I have a solution like the one below, however, my program will intermittently lock up, and I have no clue how to track this down. Here’s the pattern I use:
List subTasks = new List();
subTasks.Add(StartCoroutine(DoSubtask()));
foreach (Coroutine subtask in subtasks)
{
yield return subtask;
}
//This would yield while all tasks are working then continue down the function
while (task1 == working task2 == working task2 == working )
{
yield return 0;
}
The point is that I want all of my tasks to happen on the main thread, and actually must happen on the main thread. (You’re right though, the semantics of what I’m trying to do is like a thread join.)
It feels like this is exactly why coroutines exist, but it feels like they only designed for chains of single steps, which feels weird to me. Or, this is a bug.
What does yield return 0 give you? Does it wait one frame? I’m having trouble finding info on what yield return null does too for that matter.
As far as I can tell, your solution would work, and it’s reminiscent of condition variables / Monitors in the threading world. Its just a shame though since my condition is the completion of the subtask, rather than some work being done by the subtask and that subtask not terminating.
This may not answer the OP’s question, but one method is to have an array of values. When a coroutine is started it puts it’s unique value into this static array, and when it is finished the value is removed. At any time you can check how many coroutines are running based on the size of the array.
I ended up having to write a little class that polls once per frame. However, I am going to chalk this up to a bug in Unity.
public class CoroutineJoin
{
List _subTasks = new List();
private readonly MonoBehaviour _owningComponent;
public CoroutineJoin(MonoBehaviour owningComponent)
{
_owningComponent = owningComponent;
}
List<Coroutine> runningCoroutines = new List<Coroutine>();
foreach(Step s in stepList)
{
switch (s.operation)
{
case Step.Operation.Rotation:
runningCoroutines.Add(StartCoroutine(Rotation(currentRot, s)));
break;
case Step.Operation.Translate:
runningCoroutines.Add(StartCoroutine(Translate(currentPos, s)));
break;
}
}
foreach(Coroutine c in runningCoroutines)
{
yield return c;
}
I’m basically executing translations or rotations on an object (and can do both at the same time), which are contained in stepList. StartCoroutine returns the running coroutine so I add them to the runningCoroutines list. Then I loop through these and await them which delays the rest of the function until all coroutines are finished executing.
The examples above from @trullilulli and @rafaelgroc are valid ones but they basically let coroutines work only once previous one finished execution. Today I came up with this solution, seems to be “multi” threaded and valid.
Can anyone tell me, did I miss any case when my code will cause an issues? Thanks
UPD1: When I tried it live in app, seems like my code is not working as intended… I did some tests and when I start all coroutines via StartCoroutine it’s 6x faster (0.5) than when I call it via method above (3.2sec). Is that because I am trying to run all coroutines within a single coroutine?
This simple (async) code works really fast, almost as fast as if I am manually calling StartCoroutine in the main thread simultaneously. If anyone can come up with some idea, please update the topic.
UPD2: I have tried to change List with pure array and foreach with for loop, the same result regarding speed and unclear why coroutines are not executed each at a time =(
Will execute Animate1() completely before executing Animate2(), so this is an exellent way to wait for animations chained together in a sequence. If each animation takes 3 seconds, the total runtime of Start() will be 6 seconds.
What andrew achieves in the first attempt seems to be a merge algorithm, which merges the frames of each coroutine, since each yield return will return control to the running thread. The result is that the animations appear to run in parallel but in reality the total run time will also be 6 seconds. It will execute Animate1 Frame 1, Animate2 Frame1, Animate1 Frame 2 … etc.
I think the way to solve this as someone else have suggested is to start the 2 coroutines without yield return, and then find a mechanism of waiting until they’re done.
It could be as simple as using an integer to keep track of the number of started coroutines, and have each coroutine decrease that number when they finish
That’s exactly what I was using in one of my projects UNTIL it caused an app to freeze (in very rare cases because of device limitations). The problem is that you need to be 100% sure that the code which sets isDone to true will be executing in any possible scenarios. In case of any error during execution, the coroutine may be terminated in the middle and here you have infinity loop, because isDone will never be set to true =) To handle those cases, I added timeout, which is used to cancel waiting loop, if time runs out.
To avoid all these, we need to wait using native yield return, which will work in 100% cases. The downside is that you cannot update UI (show progress of operation) while waiting. While isDone method will give you more freedom.
BTW I found why isDone is 200ms faster than yield return version. I believe that yield return also waits when coroutine gets destructed/finalized, while isDone updates flag before that.