Wayback Machinekoobas.hobune.stream
May JUN Jul
Previous capture 13 Next capture
2021 2022 2023
1 capture
13 Jun 22 - 13 Jun 22
sparklines
Close Help
  • Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
  • Asset Store
  • Get Unity

UNITY ACCOUNT

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account
  • Blog
  • Forums
  • Answers
  • Evangelists
  • User Groups
  • Beta Program
  • Advisory Panel

Navigation

  • Home
  • Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
    • Blog
    • Forums
    • Answers
    • Evangelists
    • User Groups
    • Beta Program
    • Advisory Panel

Unity account

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account

Language

  • Chinese
  • Spanish
  • Japanese
  • Korean
  • Portuguese
  • Ask a question
  • Spaces
    • Default
    • Help Room
    • META
    • Moderators
    • Topics
    • Questions
    • Users
    • Badges
  • Home /
avatar image
1
Question by xxmariofer · Jul 09, 2020 at 10:32 AM · coroutinecoroutinesienumeratorstartcoroutine

Coroutines IEnumerator not working as expected

I am trying to stop/play one coroutine without luck, i simply create a IEnumerator variable, initialice the variable and try to start/stop the coroutine, but i am not getting the expected behaviour, i have created a simply code to reproduce my issue, i would expect the first coroutine to get stopped, start-finish again and start-finish one last time, but the result is the first coroutine finishes and no other coroutine gets called The expected log in the console should be start - start - end - start - end but the log is start - end @Bunny83 i am missing something?

 public class TEST : MonoBehaviour
 {
     IEnumerator timeOutCoroutine;
 
     IEnumerator Start()
     {
         timeOutCoroutine = TestRoutine();  
         StartCoroutine(timeOutCoroutine);
         yield return new WaitForSeconds(1f);
         StopCoroutine(timeOutCoroutine);
         StartCoroutine(timeOutCoroutine);  
         yield return new WaitForSeconds(20f);
         StartCoroutine(timeOutCoroutine);
     }
 
     IEnumerator TestRoutine()
     {
         Debug.Log("Start");
 
         float timer = 0;    
         while (timer < 10)
         {
             timer += Time.deltaTime;
             yield return null;
         }  
         Debug.Log("End");
     }
 }


-----------------------------------UPDATE--------------------------

i have added some logs to the code to see if the rest starcoroutine gets called

     yield return new WaitForSeconds(1f);
     Debug.Log("FIRST LOG");
     StopCoroutine(timeOutCoroutine);

     yield return new WaitForSeconds(1f);
     Debug.Log("SECOND LOG");
     StartCoroutine(timeOutCoroutine);

     yield return new WaitForSeconds(20f);
     Debug.Log("THIRD LOG");
     StartCoroutine(timeOutCoroutine);

the log result is: start - FIRST LOG - SECOND LOG - end - THIRD LOG

Comment
Add comment · Show 2
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image Gruffy · Jul 09, 2020 at 11:13 AM 0
Share

Try making the IEnumerator part for your Start method read private void Start() ins$$anonymous$$d :)

Completely Replace the word: IEnumerator Start() with: private void Start() If you have problems after that, then we'll go from there :) Cheers bud

avatar image xxmariofer Gruffy · Jul 09, 2020 at 12:31 PM 0
Share

Hello, Thanks for the suggestion but didnt work same issue test code, i am also updating the first question to give extra information to the issue please read it if posible :) ->

  IEnumerator timeOutCoroutine;
 
     void Start()
     {
         timeOutCoroutine = TestRoutine();
         StartCoroutine(Initialize());
     }
 
     IEnumerator Initialize()
     {
         StartCoroutine(timeOutCoroutine);
 
         yield return new WaitForSeconds(1f);
 
         StopCoroutine(timeOutCoroutine);
         StartCoroutine(timeOutCoroutine);
 
         yield return new WaitForSeconds(20f);
 
         StartCoroutine(timeOutCoroutine);
     }
 
     IEnumerator TestRoutine()
     {
         Debug.Log("Start");
 
 
         float timer = 0;
 
         while (timer < 10)
         {
             timer += Time.deltaTime;
             yield return null;
         }
 
 
         Debug.Log("End");
     }

2 Replies

· Add your reply
  • Sort: 
avatar image
8
Best Answer

Answer by Bunny83 · Jul 09, 2020 at 03:41 PM

:) I'm kinda suprised that until now nobody has asked a question like this. Even though I have explained what a coroutine actually is several times in other questions it just shows that there is still quite a misunderstanding what a coroutine actually is.


Lets just step back for a moment. Unity implements coroutines by utilizing a C# feature called "iterator blocks" or iterator methods (which are more generally known as Generator methods). C# ships with two relevant interfaces (IEnumerable and IEnumerator) as well as the special keyword "yield".


IEnumerable / IEnumerable<T>

An IEnumerable simply represents an object that can be iterated and can produce a sequence of "values". This interface is extremely simple and only defines a single method: GetEnumerator(). Calling this method creates a new instance of an iterator object that actually implements the sequence. IEnumerable can be seen as a "wrapper" for the actual IEnumerator. The main benefit is that the IEnumerable can essentially store the originally passed parameters / arguments internally and you can produce as many iterator objects you want without the need of specifying the same parameters again.


Note that the difference between the generic version and the "normal" non-generic version is that, as we will see in the next section, the actual "values" we are getting back are either just of type System.Object / object or that we actually get a specific type-safe collection of values


IEnumerator / IEnumerator<T>

This is the actual heart of the whole deal. This interface provides two main things that can be used to "iterate" over the sequence we are interested in. The only relevant things are: the parameterless method MoveNext() as well as the readonly property Current. That's all.


The method MoveNext has a return type of bool which indicates if it successfully moved to the next item in the collection. The Current property can be used to "read" the current value / item. As mentioned above this is where the generic type parameter comes into play. In the normal / non generic interface the type of this property is just "object" while in the generic version it has the type T.


Just for completeness the IEnumerator interface also implements a parameterless method called Reset() which is meant to "reset" the internal state of an iterator. What that exactly means is not really specified and I haven't really come across any usage of iterator blocks where this method is actually used. Also auto generated iterators that has been created through the use of the yield keyword do never implement this method because the compiler can never really determine what a reasonable reset would look like.


Usual usage of IEnumerables / IEnumerators

As we already mentioned those generally represent things you can "iterate over". If C# we have the foreach loop which is somewhat related to those interfaces. When you have an object that implements the "IEnumerable" interface you can just use it in a foreach loop like this:
 foreach(MyType val in myObject)
 {
     // do something with "val".
 }

This actually translates to something like this:

 var enumerator = myObject.GetEnumerator();
 while (enumerator.MoveNext())
 {
     MyType val = (MyType)enumerator.Current;
     // do something with "val".
 }

(Note I simplified it a little bit. A foreach in addition ensures that Dispose is called if the object implements IDisposable, but that's irrelevant for now).


The key here is that our original object provides a method (GetEnumerator) to create a new iterator object. This iterator object is used by the compiler to actively "iterate" through that iterator object by calling MoveNext until it returns false which indicates the end of the collection. After each call to MoveNext the Current property should return the "current value".


The yield keyword

This is where the compiler magic comes into play. Of course we can simply create our own classes / types which implement the IEnumerable / IEnumerator interfaces and implement our own logic for the MoveNext method. However the yield keyword is a powerful piece of compiler magic. Whenever you use the yield keyword inside a method a drastic change happens. First of all the method must have either IEnumerator or IEnumerable (or the generic equivalents) as return type. Any other return type is not accepted. Further more the compiler will generate a hidden internal class for you that will actually implement your "code".


I try to give you an idea what is happening. Imagine a simple example like this:

 IEnumerable<int> MyIterator(int aFrom, int aTo)
 {
     for(int i = aFrom; i < aTo, i++)
         yield return i;
 }

This harmless looking "method" will turn into two classes and a method which will look something like this:

 IEnumerable<int> MyIterator(int aFrom, int aTo)
 {
     return new ___MyIterator_Enumerable(aFrom, aTo);
 }
 private class ___MyIterator_Enumerable : IEnumerable<int>
 {
     int m_aFrom;
     int m_aTo;
     public ___MyIterator_Enumerable(aFrom, aTo)
     {
         m_aFrom = aFrom;
         m_aTo = aTo;
     }
     public IEnumerator<int> GetEnumerator()
     {
         return new ___MyIterator_Enumerator(m_aFrom, m_aTo);
     }
 }
 
 private class ___MyIterator_Enumerator : IEnumerator<int>
 {
     int m_aFrom;
     int m_aTo;
     int m_State;
     int m_Current;
     int m_i;
     public ___MyIterator_Enumerator(aFrom, aTo)
     {
         m_aFrom = aFrom;
         m_aTo = aTo;
         m_State = -1;
     }
     public int Current
     {
         get {
             if (m_State != 0)
                 throw new SomeException();
             return m_Current;
         }
     }
     public bool MoveNext()
     {
         switch(m_State)
         {
             case -1:
                 m_State = 0;
                 m_i = m_aFrom;
                 if (m_i < m_aTo) {
                     m_Current = m_i;
                     m_State = 0;
                     return true;
                 } else [
                     m_State = 1;
                     return false;
                 }
             break
             case 0:
                 m_i++;
                 if (m_i < m_aTo) {
                     m_Current = m_i;
                     return true;
                 } else [
                     m_State = 1;
                     return false;
                 }
             break;
             case 1:
             default:
             return false;
         }
     }
     public void Reset()
     {
         throw new NotImplementedException();
     }
 }


This is a quite drastic change. Our original method does not contain any of the code we have actually typed in the body. Instead the body of our method has been torn apart and turned into a statemachine object. This generated class is hidden. You won't see this code anywhere unless you decompile your code into IL then you will actually find that internal class.


What's important to realise is that our method does no longer execute the code we've written but just creates an instance of a class. So it creates an instance of that iterator class. Note I specifically made an IEnumerable example just for completeness. If you specify "IEnumerator" as return type that IEnumerable wrapper class just doesn't exist and our method would directly return the IEnumerator class instance instead.


How Unity uses iterators to implement coroutines

Iterators or generators are mainly meant to iterate over / "generate" values of some kind. However since the C# compiler can magically turn our linear code into a statemachine that splits our code apart at the points where we yield a value, we can actually use that "method to statemachine" magic to implement coroutines.


While in "normal" iterators we are usually interested in the values that are produced by the iterator, we as developers are more interested in the side effects that is in between those yield instructions. When you "call" your coroutine method you don't call any of your code. Keep in mind that all you do is creating a new instance of that statemachine object that the compiler generated for your method. You now pass this object instance to the StartCoroutine method. This does two things at once:

First Unity will create and instance of it's internal "Coroutine" class to managed your coroutine internally and store that iterator object that you passed to StartCoroutine alongside for later use. In addition it will immediately call MoveNext once on that object. This "advance" your statemachine to the next yield statement. The scheduler then will look at the value that you yielded by examin the Current property. Based on that value the scheduler decides what to do next. We don't know the exact implementation of Unity's scheduler since it's implemented in native C++ code. However it most likely will just add that coroutine to a certain "waiting queue". So when you yield "null" the scheduler probably just adds the coroutine to a list of coroutines that should be resumed the next frame. When you yield "WaitForEndOfFrame" it just stores that coroutine in a list that gets processed at the end of the frame.


Each time a coroutine is "resumed" the scheduler simply calls MoveNext and again checks the yielded value when the coroutine wants to be resumed. Of course as soon as MoveNext returns false the coroutine / iterator has finished (either reached the end or a yield break; was reached).


Conclusion

Hopefully this clears up the confusion what is happening in your case. Though just in case I will give a quick run-through:


In Start you call your coroutine to create a new statemachine instance. This instance is stored in your "timeOutCoroutine" variable. When you call StartCoroutine(timeOutCoroutine); you pass your statemachine to the Unity scheduler and ask it to start a new coroutine. The scheduler will happily call MoveNext on your object and queue the coroutine whereever it needs to be.


When you call StopCoroutine(timeOutCoroutine); you just tell Unity to remove the coroutine with this iterator object from any scheduled events, thus will terminate the coroutine. However your statemachine object of course is still in the same state. When you re-schedule your iterator object, Unity starts a new coroutine with your iterator object. Though it should be obvious that the statemachine will just continue where you left off when you stopped the coroutine.


Solution

If you want a coroutine to be restarted you would need to create a new instance of your statemachine object by calling your generator method again to get a fresh instance. As I mentioned the IEnumerator interface has a Reset method, however the compiler magic does not implement it at all as for most custom iterators there's no sensible way how the compiler could figure out what needs to be done in order to "reset" it.


Note that in some cases it might by useful to implement coroutines as an IEnumerable instead of IEnumerator. Though this only makes sense when your coroutine has parameters. The IEnumerable essentially can capture those parameters. So when you want to start a new instance of your coroutine you can just use the IEnumerable object and call GetEnumerator on it to get a new IEnumerator object. This might be useful in some OOP / abstract code that should be able to start arbitrary coroutines.

Comment
Add comment · Show 1 · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image xxmariofer · Jul 09, 2020 at 04:05 PM 0
Share

Thanks a lot for such an In deep an awesome explanation, I will have to take a second or third read to fully understand the internal functuonality. It is the most fully detail explanation I have found, I think it should get wikified. Thanks as always!

avatar image
0

Answer by TheAwesomeLyfe · Jul 09, 2020 at 11:27 AM

@xxmariofer Try Adding a delay between Start Coroutine and Stop Coroutine. From what I see in your code, you are starting the Coroutine and after one second it's supposed to stop by calling stop. However since start Coroutine is called after it, this happens immediately giving you the unwanted result as the Coroutine never stopped.

IEnumerator timeOutCoroutine; public float m_Delay = 0.5f; IEnumerator Start() { timeOutCoroutine = TestRoutine(); StartCoroutine(timeOutCoroutine); yield return new WaitForSeconds(1f); StopCoroutine(timeOutCoroutine); //Add a delay here yield return new WaitForSeconds(m_Delay); StartCoroutine(timeOutCoroutine); yield return new WaitForSeconds(20f); StartCoroutine(timeOutCoroutine); }

If it seems like the delay isn't long enough and it starts to fast after stop, just increase the m_Delay value to something higher than 0.5.

Hope this helps.

---Update--- @xxmariofer

I tested your script and i saw where the coroutine will start but not stop after one second of your WaitForSeconds in the Start Method. So i removed the " timeOutCoroutine = TestRoutine();" and instead used TestRoutine() directly in the StartCoroutine() Function.

Example : StartCoroutine(TestRoutine());

The Delays i mentioned earlier is no longer needed as when i tested the new Solution it seems to work on my end. Here is the Code

 using System.Collections;
 using System.Collections.Generic;
 using UnityEngine;
 
 public class TestScript : MonoBehaviour
 {
     IEnumerator timeOutCoroutine;
 
     IEnumerator Start()
     {
         
         StartCoroutine(TestRoutine());
         yield return new WaitForSeconds(1f);
         StopCoroutine(TestRoutine());
         StartCoroutine(TestRoutine());
         yield return new WaitForSeconds(20f);
         StartCoroutine(TestRoutine());
     }
 
     IEnumerator TestRoutine()
     {
         Debug.Log("Start");
 
         float timer = 0;
         while (timer < 10)
         {
             timer += Time.deltaTime;
             yield return null;
         }
         Debug.Log("End");
     }
 }
 

Comment
Add comment · Show 3 · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image xxmariofer · Jul 09, 2020 at 12:32 PM 0
Share

Hello thanks for posting, i have updated the question with extra information

tested your suggestion without luck continues the same behaviour here is the updated code ->

     IEnumerator timeOutCoroutine;
 
     void Start()
     {
         timeOutCoroutine = TestRoutine();
         StartCoroutine(Initialize());
     }
 
     IEnumerator Initialize()
     {
         StartCoroutine(timeOutCoroutine);
 
         yield return new WaitForSeconds(1f);
 
         StopCoroutine(timeOutCoroutine);
 
         yield return new WaitForSeconds(1f);
 
         StartCoroutine(timeOutCoroutine);
 
         yield return new WaitForSeconds(20f);
 
         StartCoroutine(timeOutCoroutine);
     }
 
     IEnumerator TestRoutine()
     {
         Debug.Log("Start");
 
         float timer = 0;
 
         while (timer < 10)
         {
             timer += Time.deltaTime;
             yield return null;
         }
 
 
         Debug.Log("End");
     }
avatar image TheAwesomeLyfe xxmariofer · Jul 09, 2020 at 02:20 PM 0
Share

@xxmariofer I will play around with this and find a better and more effective solution. I will update the answer shortly for you to try.

avatar image xxmariofer TheAwesomeLyfe · Jul 09, 2020 at 03:28 PM 0
Share

Ok thanks! A quick hack is to simply have a variable cancel and check every iteration if the variable is true, and if it is simply break the coroutine but if the coroutines get complex would add extra unneedee complexity, maybe it is a bug? It should simply work with the first code, shouldnt it?

Your answer

Hint: You can notify a user about this post by typing @username

Up to 2 attachments (including images) can be used with a maximum of 524.3 kB each and 1.0 MB total.

Follow this Question

Answers Answers and Comments

145 People are following this question.

avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image

Related Questions

Is there a way to show a warning when a Coroutine is called directly? 0 Answers

How do Coroutines behave with Triggers? 1 Answer

Coroutine runs Very Slow for First time Call 1 Answer

coroutine in update method 1 Answer

Coroutine won't stop running. Tried String. 1 Answer


Enterprise
Social Q&A

Social
Subscribe on YouTube social-youtube Follow on LinkedIn social-linkedin Follow on Twitter social-twitter Follow on Facebook social-facebook Follow on Instagram social-instagram

Footer

  • Purchase
    • Products
    • Subscription
    • Asset Store
    • Unity Gear
    • Resellers
  • Education
    • Students
    • Educators
    • Certification
    • Learn
    • Center of Excellence
  • Download
    • Unity
    • Beta Program
  • Unity Labs
    • Labs
    • Publications
  • Resources
    • Learn platform
    • Community
    • Documentation
    • Unity QA
    • FAQ
    • Services Status
    • Connect
  • About Unity
    • About Us
    • Blog
    • Events
    • Careers
    • Contact
    • Press
    • Partners
    • Affiliates
    • Security
Copyright © 2020 Unity Technologies
  • Legal
  • Privacy Policy
  • Cookies
  • Do Not Sell My Personal Information
  • Cookies Settings
"Unity", Unity logos, and other Unity trademarks are trademarks or registered trademarks of Unity Technologies or its affiliates in the U.S. and elsewhere (more info here). Other names or brands are trademarks of their respective owners.
  • Anonymous
  • Sign in
  • Create
  • Ask a question
  • Spaces
  • Default
  • Help Room
  • META
  • Moderators
  • Explore
  • Topics
  • Questions
  • Users
  • Badges