Search Unity

finally block not executing in a stopped coroutine

Discussion in 'Scripting' started by gwiazdorrr, Apr 22, 2015.

  1. gwiazdorrr

    gwiazdorrr

    Joined:
    Sep 29, 2014
    Posts:
    102
    Consider following coroutine:

    Code (CSharp):
    1. private System.Collections.IEnumerator CoroutineBody()
    2. {
    3.     try
    4.     {
    5.         Debug.Log("try");
    6.         for (;;)
    7.         {
    8.             yield return null;
    9.         }
    10.     }
    11.     finally
    12.     {
    13.         Debug.Log("finally");
    14.     }
    15. }
    I noticed that when coroutine get stopped with StopCoroutine, the finally block never gets executed. It also affect using blocks.

    System.Collections.IEnumerator doesn't implement IDisposable itself, but types created by the compiler when yield return is used do. This is valid:

    Code (CSharp):
    1. var coro = CoroutineBody();
    2. coro.MoveNext();
    3. ((IDisposable)coro).Dispose();
    For example, foreach loop will call Dispose() on used enumerators. (By the way, System.Collections.Generic.IEnumerator<T> implements IDisposable, so this is made more apparent.)

    What's the big deal? It breaks deterministic resource release. This might illustrate it better:

    Code (CSharp):
    1. private System.Collections.IEnumerator WriteByteByByte(string path, byte[] data)
    2. {
    3.     using (var file = System.IO.File.OpenWrite(path))
    4.     {
    5.         foreach (var b in data)
    6.         {
    7.             file.WriteByte(b);
    8.             yield return null;
    9.         }
    10.     }
    11. }
    If you happen to stop this coroutine, the file will not be closed until GC collects it; the file will be impossible to read from / write to in meantime, which might last nobody knows how long.

    Any idea why this happens? Are there any plans to alter this behaviour in future? Any reasonable workarounds?
     
    kdchabuk and LazloBonin like this.
  2. makeshiftwings

    makeshiftwings

    Joined:
    May 28, 2011
    Posts:
    3,350
    I believe this is a feature of IEnumerator in .NET rather than anything specific to Unity:

    http://blogs.msdn.com/b/dancre/arch...nd-usings-your-dispose-may-not-be-called.aspx

    I guess Unity could call IDisposable.Dispose from StopCoroutine but then that would destroy the iterator, which might break user code that assumes it doesn't. I think the workaround is to just call dispose yourself after calling StopCoroutine:

    Code (csharp):
    1. var coroutine = MyCoroutine();
    2. StartCoroutine(coroutine);
    3.  
    4. ...
    5.  
    6. StopCoroutine(coroutine);
    7. ((IDisposable)coroutine).Dispose();
     
    gwiazdorrr and eisenpony like this.
  3. gwiazdorrr

    gwiazdorrr

    Joined:
    Sep 29, 2014
    Posts:
    102
    Since most (all?) IEnumerators implement IDisposable, the rule of thumb is call Dispose whenever you're done with IEnumerator. Unity doesn't do this and it leads to finally block not being executed, which should never happen.

    Wasn't aware there's StopCoroutine accepting IEnumerator, thanks. However, keeping track of every coroutine running is not convenient.

    Ideally there should be a way to tell the unity to dispose a coroutine on stopping.
     
  4. shaderop

    shaderop

    Joined:
    Nov 24, 2010
    Posts:
    942
    @gwiazdorrr, I was rather surprised that your observation is right. For the most part.

    As you have noted, Dispose in IDisposable doesn't get called when the coroutine is stopped. But what seems to be happening instead is that the finalizer will be called instead, or this seems to be the case according to the little test case I wrote.

    So you don't need to worry about files not getting closed and resources not getting freed because these are unmanaged resources and will get freed in the finalizer, and the finalizer is getting called on exit from the coroutine.

    But this leaves the small issue of managed resources, like unsubscribing from events or returning objects to pools or what have you. There are a few workarounds I can think of, the most recent of which is a blog post by @AngryAnt on adding several enhancement to coroutines, one of them being the concept of soft stops, which would allow a coroutine to check if it's time to stop and do whatever it needs before actually stopping.

    Again, you only need to do this if you need to dispose of managed resources. Files, network sockets, and even database connection should get freed just fine.
     
  5. gwiazdorrr

    gwiazdorrr

    Joined:
    Sep 29, 2014
    Posts:
    102
    @shaderop: You are wrong.

    This is not how finalizers work. You can't cherry pick an object and finalize it -- it's GC's job.

    In your test case your IEnumerator happens to get collected and finalizer gets called -- evantually this is going to happen to all the unreferenced objects. But do you know when, exactly? Is it immediately after StopCoroutine? At the end of the current frame? A few frames after that?

    I prepared test case of my own.

    Code (CSharp):
    1. using System;
    2. using UnityEngine;
    3.  
    4. public class CoroutineTest : MonoBehaviour
    5. {
    6.     public bool restartCoroutine;
    7.     private System.Collections.IEnumerator m_coroutine;
    8.  
    9.     void Update()
    10.     {
    11.         if ( restartCoroutine )
    12.         {
    13.             if (m_coroutine != null)
    14.             {
    15.                 StopCoroutine(m_coroutine);
    16.             }
    17.             StartCoroutine(m_coroutine = new TestEnumerator());
    18.             restartCoroutine = false;
    19.         }
    20.     }
    21.  
    22.     public class TestEnumerator : System.Collections.IEnumerator, IDisposable
    23.     {
    24.         public TestEnumerator() { Debug.Log("STARTED"); }
    25.         ~TestEnumerator() { Debug.Log("FINALISED"); }
    26.  
    27.         public object Current { get { return null; } }
    28.         public bool MoveNext() { return true; }
    29.         public void Reset() { throw new NotImplementedException(); }
    30.         public void Dispose() { Debug.Log("DISPOSED"); }
    31.     }
    32. }
    Keep toggling restartCoroutine in the inspector in runtime. What you'll see in console is something along these lines:
    STARTED
    STARTED
    STARTED
    FINALISED
    FINALISED
    FINALISED
    etc.

    Collection happens from time to time, definitely not during or immediately after StopCoroutine. So when saying:

    You couldn't be more wrong. You need to worry about files not getting closed and resources not getting freed, because you don't know when they're going to be disposed had you failed to call Dispose.
     
    kdchabuk likes this.
  6. shaderop

    shaderop

    Joined:
    Nov 24, 2010
    Posts:
    942
    You silver-tongued charmer, you. How can I resist engaging in constructive discussion with you when you lay it down like that. :)

    Thanks for explaining the Garbage Collector for my benefit. You really shouldn't have.

    But regardless, this is what was happening in my test case. The finalizers for objects inside the coroutine where invoked directly after the coroutine was stopped.

    Since you showed me yours, I'll show you mine, which is considerably larger.

    First, the disposable: Notice it prints different messages depending on whether it was finalized or disposed:
    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. public class DisposeMe : System.IDisposable
    4. {
    5.     bool disposed = false;
    6.  
    7.     // Public implementation of Dispose pattern callable by consumers.
    8.     public void Dispose()
    9.     {
    10.         Debug.Log(string.Format("Disposed"));
    11.         Dispose(true);
    12.         System.GC.SuppressFinalize(this);
    13.     }
    14.  
    15.     // Protected implementation of Dispose pattern.
    16.     protected virtual void Dispose(bool disposing)
    17.     {
    18.         if (disposed)
    19.             return;
    20.  
    21.         if (disposing)
    22.         {
    23.             // Free any other managed objects here.
    24.             //
    25.         }
    26.  
    27.         // Free any unmanaged objects here.
    28.         //
    29.         disposed = true;
    30.     }
    31.  
    32.     ~DisposeMe()
    33.     {
    34.         Debug.Log(string.Format("Finalized"));
    35.         Dispose(false);
    36.     }
    37. }
    38.  
    Which is used in this, which runs a coroutine for a second before stopping it:
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class CoroutineDisposeTester : MonoBehaviour
    5. {
    6.     private float timeToTerminateCoroutine;
    7.     private IEnumerator disposeTester;
    8.  
    9.     void Start()
    10.     {
    11.         timeToTerminateCoroutine = 1f;
    12.  
    13.         disposeTester = DisposeTest();
    14.         StartCoroutine(disposeTester);
    15.     }
    16.  
    17.     void Update()
    18.     {
    19.         timeToTerminateCoroutine -= Time.deltaTime;
    20.         if (timeToTerminateCoroutine < 0 && disposeTester != null)
    21.         {
    22.             StopCoroutine(disposeTester);
    23.             disposeTester = null;
    24.         }
    25.     }
    26.  
    27.     private IEnumerator DisposeTest()
    28.     {
    29.         using (new DisposeMe())
    30.         {
    31.             while (true)
    32.             {
    33.                 yield return new WaitForEndOfFrame();
    34.             }
    35.         }
    36.     }
    37. }
    38.  
    The result is that "Finalized" is printed to the console a second and change after I start playing the scene in Unity.

    Now, there is no reason for the finalizer to be called this soon since the game is still running and memory is a vast open void of emptiness. Yet it was obviously prioritized for garbage collection for some reason.

    As evident from this test: A few frames after stopping, probably the times it takes for the GC thread to take its turn. Perhaps that is not precise enough for you taste, but it's far from being completely undeterministic.

    Not sure how your test case is relevant to the problem you originally stated. Your issue is with disposing of objects in the coroutine and yet you're fiddling with the enumerator. It's like suspecting that water in a bottle tastes funny and testing it by chewing on the bottle.

    Again, the finalizer is getting called a few frames after the coroutine is stopped in my test case. If that is not exact enough for you, then good luck finding a workaround for that, and please look at the solution suggested in my previous post. But "a few frames after" is far from "you don't know when they're going to be disposed."

    Enough with the charm offensive already. You're making me blush. ;)
     
    KelsoMRK likes this.
  7. gwiazdorrr

    gwiazdorrr

    Joined:
    Sep 29, 2014
    Posts:
    102
    Spare the irony, please.

    If you Update in your code to the code below "a few frames after" changes to "a few seconds after, in batches".

    Code (CSharp):
    1.     void Update()
    2.     {
    3.         timeToTerminateCoroutine -= Time.deltaTime;
    4.         if (timeToTerminateCoroutine < 0 && disposeTester != null)
    5.         {
    6.             StopCoroutine(disposeTester);
    7.             Start();
    8.         }
    9.     }
    The only way something inside coroutine can get finalized is when the IEnumerator itself gets finalized.

    ^^^ This is how the most nasty bugs are born.

    Have you tested in on your target platform? Without the editor attached? Under high/low GC pressure? Without the knowledge of Unity inner workings you can never be sure when the finalization happens, hence you should never assume resources have been released in stoppable coroutine, if no workaround is in place.
     
  8. shaderop

    shaderop

    Joined:
    Nov 24, 2010
    Posts:
    942
    You forgot the "or else" part. Tough Internet guys need to be tough.

    Also that was sarcasm, not irony. Look it up.

    I'll take your word for it. But why would a sane, none-bottle-chewing person call Start from within Update? Desperation to prove a point doesn't count, by the way.

    Citation needed. And you still insist on not testing the thing that you supposedly care about, i.e. disposing of objects inside a stopped coroutine.

    And I suppose you know that by your many years of experience and numerous battle scars (Or battle acne as the case may very well be). I also think you should do a better job of mining your experience for lessons learned if you really think that making an assumption and then testing that assumption with actual code, or what is often referred to as the "the scientific method", is "how the nastiest bugs are born."

    No. Have you? Because it's you who brought this problem up, and it is you who is supposedly seeking an answer. If you really care for an answer, then go ahead, knock yourself out testing it until your convinced either way. My own curiosity is satisfied, thank you very much.

    If you're not satisfied with the results provided (because apparently you can't trust yourself not to call Start from within Update) and if you won't commit to testing it in scenarios that matter to you, then a deterministic, safe solution was offered in my first post. You might want to take a look at that if you are really searching for a solution.

    But as the bear said, "You're not really here for the hunting, are you?"
     
    Mycroft likes this.
  9. gwiazdorrr

    gwiazdorrr

    Joined:
    Sep 29, 2014
    Posts:
    102
    Wow, dude. Stop being childish. Also, not sure if you're trolling or not, but perhaps you should be the one looking up sarcasm in a dictionary.

    Also, calling Start from within Update is fine, if you know what you're doing. In this case it starts coroutine again, thus provides us with more test samples.

    No, what you suggest is not scientific -- it is the exact opposite. You are ignoring the way GC works.

    Test cases you and I created only tell us that finally block is not run. The fact, that unreferenced objects get collected at some point is nothing new. There is no way to get the upper limit for how long until an object is going to be finalized as far as I know, thus relying on GC to release unmanaged resources is dangerous.

    Unless you tell GC to collect, which you shouldn't.
     
  10. shaderop

    shaderop

    Joined:
    Nov 24, 2010
    Posts:
    942
    I think you need quite a few definitions cleared up for you.

    Sarcasm is me referring to your battle scars as "battle acne" in an attempt to ridicule and undermine your unsubstantiated appeal to your own authority.

    Irony is you talking like a 10-year old and trying to wow-dude me into stopping "being childish."

    Irony is also you telling me to look up the difference between the two when you have no clue what "irony", "sarcasm", "trolling", or "scientific method" even mean.

    The "scientific method" is, and I quote, "the overall process involves making a hypothesis [i.e. disposal objects inside a stopped coroutine need immediate clean up], deriving predictions from them as logical consequences [i.e. this clean up must happen soon after the coroutine is stopped and not be deferred until regular garbage collection], and then carrying out experiments based on those predictions to determine whether the original hypothesis was correct [See my code example]."

    The exact opposite of that, believe it or not, would be what you're doing, i.e. parroting received dogma (Thou shall not know when the GC is invoked) with no regard to the presented evidence.

    I think you knowing what you're doing is a big known unknown at this point. I also think that most people on this forum would agree that calling Start from Update puts you firmly in in the "doesn't know what he or she is doing" category.

    Mine didn't test and has nothing to do with finally blocks, which is apparent from not having the "finally" keyword anywhere in it. So you might want to go over that once more.

    The experiment above shows that it is in fact possible to know when an object will be finalized in this scenario. It is you who is insisting on ignoring it based on nothing more than received dogma. If you really want to prove it wrong, then follow your own suggestion and test it under varying conditions and measure the results for yourself.

    Are you quoting from a book of C# platitudes or .NET truisms? Why else would you throw in that little gem when no one has mentioned GC supression before in this thread and it has no bearing on the discussion at hand. Why not throw in a little song and dance number about Boolean short circuiting while you are at it?

    I would like to point out once more that both an answer and a solution were provided. And yet you continue to rail against the answer because it has not been tested universally enough for your taste, and you continue to ignore the solution that works around this issue.
     
  11. gwiazdorrr

    gwiazdorrr

    Joined:
    Sep 29, 2014
    Posts:
    102
    Here you are, arguing about words' definitions over the internet. Well done. Seriously, go look it up and be done with it.

    As for the rest of your post, it is more concerning than amusing, since in your arrogance you keep spreading misinformation about the subject matter and fail to engage in any meaningful discussion. You are so wrong, yet full of yourself. Clearly a waste of time.

    Ignore.
     
    piotrekwitek and Mycroft like this.
  12. KelsoMRK

    KelsoMRK

    Joined:
    Jul 18, 2010
    Posts:
    5,539
    You need to learn some forum etiquette. @shaderop provided valuable information complete with a detailed code sample (while also mentioning that your initial observations were correct I might add). You chose to use combative language and start an argument about semantics.

    And yes - calling Start from Update is a dumb pattern.
     
  13. gwiazdorrr

    gwiazdorrr

    Joined:
    Sep 29, 2014
    Posts:
    102
    I really don't see how my language could be seen as combatative, but you obviously may have a point here. Anyway, it wasn't my intention.

    As for Start from Update, it was just the smallest code modification needed to get desired behaviour. Let's not make a pattern out of it.

    EDIT: To put discussion back on its track and to further illustrate the seriousness of the issue, consider this test code:

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using System.IO;
    4. using System;
    5.  
    6. public class CoroutineTest1 : MonoBehaviour
    7. {
    8.     public int DataLength = 1024;
    9.     public float WritePeriod = 1.0f;
    10.     public bool StartWrite = false;
    11.     public bool StopWrite = false;
    12.     public string FilePath = "test.dat";
    13.  
    14.     private IEnumerator m_coroutine;
    15.     private System.Diagnostics.Stopwatch m_waitForFinalizeTimer = new System.Diagnostics.Stopwatch();
    16.  
    17.    
    18.     void Update ()
    19.     {
    20.         if ( StartWrite )
    21.         {
    22.             try
    23.             {
    24.                 // this should throw if coro's still running
    25.                 var coro = FileWritingCoroutine(FilePath, new byte[DataLength]);
    26.                 m_coroutine = coro;
    27.                 StartCoroutine(m_coroutine);
    28.                 Debug.Log("Coroutine started");
    29.             }
    30.             finally
    31.             {
    32.                 StartWrite = false;
    33.             }
    34.         }
    35.  
    36.         if (StopWrite)
    37.         {
    38.             if ( m_coroutine != null )
    39.             {
    40.                 Debug.Log("Stopping...");
    41.                 StopCoroutine(m_coroutine);
    42.                 m_coroutine = null;
    43.                 m_waitForFinalizeTimer.Reset();
    44.                 m_waitForFinalizeTimer.Start();
    45.             }
    46.             StopWrite = false;
    47.         }
    48.  
    49.         // see if the file's accessible
    50.         if ( m_waitForFinalizeTimer.IsRunning )
    51.         {
    52.             try
    53.             {
    54.                 using ( File.OpenRead(FilePath) )
    55.                 {
    56.                 }
    57.  
    58.                 Debug.LogFormat("It took {0} for the file to close", m_waitForFinalizeTimer.Elapsed);
    59.                 m_waitForFinalizeTimer.Stop();
    60.             }
    61.             catch ( Exception )
    62.             {
    63.                 // file is still opened
    64.             }
    65.         }
    66.     }
    67.  
    68.     private IEnumerator FileWritingCoroutine(string path, byte[] data)
    69.     {
    70.         using (var file = File.OpenWrite(path))
    71.         {
    72.             while (true)
    73.             {
    74.                 file.Write(data, 0, data.Length);
    75.                 yield return new WaitForSeconds(WritePeriod);
    76.             }
    77.         }
    78.     }
    79. }
    80.  
    By toggling checkboxes you start and stop the file writing coroutine. The script then waits for the file to become readable again.

    In empty environment, it takes anywhere from 0.5s to 6s for the file to get closed. When it gets CPU intensive, it raises to dozens of seconds. Contrary to what has been written, it doesn't seem objects created in a coroutine are in "flagged" in a special way to speed up the collection.
     
    Last edited: Apr 22, 2015
  14. shaderop

    shaderop

    Joined:
    Nov 24, 2010
    Posts:
    942
    QFA
     
  15. JeanSimonet

    JeanSimonet

    Joined:
    Nov 23, 2012
    Posts:
    31
    I just ran into this myself, and I would really like it if the coroutine iterator was disposed when StopCoroutine() is called (which would call the finally block, as per the specifications of iterator blocks). It's not a bug per se, nothing actually says anywhere that the coroutine iterator shouldn't be kept around and disposed only when finalized, but conceptually, you imagine the coroutine to be properly cleaned up after you call StopCoroutine(). It would make housekeeping a lot more straightforward.
    (More info on iterator blocks and finally: http://csharpindepth.com/articles/chapter6/iteratorblockimplementation.aspx)
     
  16. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,527
    Hrmmm... never thought of this. Might have to implement this in my RadicalCoroutine.
     
    ThermalFusion likes this.
  17. eisenpony

    eisenpony

    Joined:
    May 8, 2015
    Posts:
    974
    I'm not sure it makes sense for the iterator to be disposed when you call StopCoroutine. How would this kind of code be handled?

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class CoroutineDisposeTester : MonoBehaviour
    5. {
    6.     private float timeToTerminateCoroutine;
    7.     private IEnumerator disposeTester;
    8.  
    9.     void Start()
    10.     {
    11.         timeToTerminateCoroutine = 1f;
    12.  
    13.         disposeTester = DisposeTest();
    14.         StartCoroutine(disposeTester);
    15.     }
    16.  
    17.     void Update()
    18.     {
    19.         timeToTerminateCoroutine -= Time.deltaTime;
    20.         if (timeToTerminateCoroutine < 0 && disposeTester != null)
    21.         {
    22.             StopCoroutine(disposeTester);
    23.  
    24.             timeToTerminateCoroutine = 1f;
    25.             StartCoroutine(disposeTester);
    26.         }
    27.     }
    28.  
    29.     private IEnumerator DisposeTest()
    30.     {
    31.         using (new DisposeMe())
    32.         {
    33.             while (true)
    34.             {
    35.                 yield return new WaitForEndOfFrame();
    36.             }
    37.         }
    38.     }
    39. }
    Or (potentially) worse:
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class CoroutineDisposeTester : MonoBehaviour
    5. {
    6.     private float timeToTerminateCoroutine;
    7.     private IEnumerator disposeTester;
    8.  
    9.     void Start()
    10.     {
    11.         timeToTerminateCoroutine = 1f;
    12.  
    13.         disposeTester = DisposeTest();
    14.         StartCoroutine(disposeTester);
    15.         StartCoroutine(disposeTester);
    16.     }
    17.  
    18.     void Update()
    19.     {
    20.         timeToTerminateCoroutine -= Time.deltaTime;
    21.         if (timeToTerminateCoroutine < 0 && disposeTester != null)
    22.         {
    23.             StopCoroutine(disposeTester);
    24.         }
    25.     }
    26.  
    27.     private IEnumerator DisposeTest()
    28.     {
    29.         using (new DisposeMe())
    30.         {
    31.             while (true)
    32.             {
    33.                 yield return new WaitForEndOfFrame();
    34.             }
    35.         }
    36.     }
    37. }
    I know the example is contrived but they usually are..

    Anyways, it seems reasonable to keep a coroutine reference around so you can activate it again later. If Dispose was called when calling StopCoroutine this would break in weird ways. As the consumer of this framework, I'd rather have control over whether the Coroutine is disposed or not, so what's wrong with requiring an explicit call to IDisposable.Dispose or waiting for the GC? You could even create an extension method on MonoBehaviour called StopAndDisposeCoroutine if you really dislike the extra line of code.
     
  18. JeanSimonet

    JeanSimonet

    Joined:
    Nov 23, 2012
    Posts:
    31
    @eisenpony Unless I am mistaken, I think that when you call StartCoroutine() for the second time, a brand NEW coroutine instance starts up. A new iterator block is allocated with all its fake stack data. In fact, that's the only way you can start the same coroutine multiple times.

    Also, you can't really Dispose the Coroutine object, because it is not the same as the Coroutine iterator block that Unity allocates internally (and is enumerating over, and actually stores all the fake stack variables). The Coroutine object is just a handle that you can pass back to Unity so it knows which instance you want to stop, or wait for (in the case of a coroutine yield returning another coroutine). Notice that there isn't a StartCoroutine(Coroutine routine) variant. That's because StopCoroutine() and StartCoroutine() don't mean 'pause' and 'resume', they really do mean 'destroy' and 'create', respectively.

    Which is why, to me at least, it would seem perfectly acceptable for Unity to dispose the iterator block when you call StopCoroutine(). Again, it's not a bug, the iterator block will eventually be GCd (and therefore disposed), but it would be much MUCH more practical if the Dispose() happened right away.
     
    LazloBonin likes this.
  19. eisenpony

    eisenpony

    Joined:
    May 8, 2015
    Posts:
    974
    That would be true if you were calling StartCoroutine using the function which returns an IEnumerator. However, by assigning the IEnumerator to a variable first, we can start the Coroutine using the same IEnumerator from where we left off.

    For instance:

    Code (csharp):
    1. public class CoroutineTest : MonoBehaviour {
    2.  
    3.   IEnumerator theRoutinesIEnumerator;
    4.   UnityEngine.Coroutine theRoutine;
    5.   int frameCount = 0;
    6.  
    7.   void Start () {
    8.     theRoutinesIEnumerator = Counting();
    9.     theRoutine = StartCoroutine (theRoutinesIEnumerator);
    10.   }
    11.  
    12.   void Update () {
    13.     if (frameCount % 10 == 0)
    14.     {
    15.       StopCoroutine (theRoutine);
    16.       Debug.Log("got to frame: " + frameCount);
    17.       theRoutine = StartCoroutine (theRoutinesIEnumerator);
    18.     }
    19.   }
    20.  
    21.   IEnumerator Counting()
    22.   {
    23.     for (frameCount = 0; true; frameCount ++)
    24.       yield return new WaitForEndOfFrame();
    25.   }
    26. }
    This generates the following output in my log:
    got to frame: 0
    got to frame: 10
    got to frame: 20
    got to frame: 30
    got to frame: 40

    etc...

    I can recreate the Enumerator each time as you suggest by doing:
    Code (csharp):
    1.   void Update () {
    2.     if (frameCount % 10 == 0)
    3.     {
    4.       StopCoroutine (theRoutine);
    5.       Debug.Log("got to frame: " + frameCount);
    6.       theRoutine = StartCoroutine (Counting());
    7.     }
    8.   }
    In which case I see this:
    got to frame: 0
    got to frame: 10
    got to frame: 10
    got to frame: 10
    got to frame: 10

    I modified my original code to explicitly dispose the IEnumerator just to see what would happen.
    Code (csharp):
    1.   void Update () {
    2.     if (frameCount % 10 == 0)
    3.     {
    4.       StopCoroutine (theRoutine);
    5.       ((IDisposable)theRoutinesIEnumerator).Dispose();
    6.       Debug.Log("got to frame: " + frameCount);
    7.       theRoutine = StartCoroutine (theRoutinesIEnumerator);
    8.     }
    9.   }
    resulted in:
    got to frame: 0
    got to frame: 10
    NullReferenceExpception
    NullReferenceExpception
    NullReferenceExpception
    NullReferenceExpception

    So I'm glad the call to StopCoroutine doesn't dispose my iterator. It is mine after all, I should be responsible for disposing it.

    You're right about that. I tried casting Coroutine as IDisposable and got a "Cannot convert" compiler error. I had misread makeshiftwings code above and assumed I could do this.
     
    Last edited: Aug 7, 2015
    gwiazdorrr and Deleted User like this.
  20. JeanSimonet

    JeanSimonet

    Joined:
    Nov 23, 2012
    Posts:
    31
    Crap! Of course, you're right! If you store the IEnumerator, then there is no reallocation, and Start and Stop act as Pause and Resume! Then, yeah, it totally doesn't make sense for StopCoroutine() to dispose the enumerator!

    Also, you did give me a solution, which is to store, stop and dispose the enumerator myself, rather than dealing with the Coroutine object at all. Thanks!
     
    gwiazdorrr likes this.
  21. JeanSimonet

    JeanSimonet

    Joined:
    Nov 23, 2012
    Posts:
    31
    By the way, for anyone else wanting to 'manage' their coroutines manually, you do need to deal with them slightly differently, by storing and using IEnumerator instead of Coroutine. As pointed out by @gwiazdorrr, you can actually cast the IEnumerator created by an iterator blocks to the IDisposable interface type and call Dispose on it.

    Here is a more complete example, showing when it's useful to manage your coroutine's lifecycle.
    Code (CSharp):
    1.     IEnumerator MyCoroutine()
    2.     {
    3.         try
    4.         {
    5.             Debug.Log("I'm doing something important, like opening a file");
    6.             while (true)
    7.             {
    8.                 Debug.Log("Do stuff until somebody kills the coroutine");
    9.                 yield return new WaitForSeconds(1.0f);
    10.                 using (SomeClass importantObject = new SomeClass())
    11.                 {
    12.                     // This is using another important object that should
    13.                     // be cleaned up if the coroutine is stopped while in
    14.                     // this wait block, but ONLY in this case.
    15.                     yield return new WaitForSeconds(2.0f);
    16.                 }
    17.  
    18.                 // If the coroutine is stopped during THIS wait, then it won't try
    19.                 // to do anything with the 'importantObject', but it will still
    20.                 // execute the finally block.
    21.                 yield return new WaitForSeconds(1.0f);
    22.             }
    23.         }
    24.         finally
    25.         {
    26.             Debug.Log("I'm cleaning up after myself, closing the file I opened");
    27.         }
    28.     }
    29.  
    30.     IEnumerator coroutineInstance;
    31.     void StartCoroutine()
    32.     {
    33.         coroutineInstance = MyCoroutine();
    34.         StartCoroutine(coroutineInstance);
    35.     }
    36.  
    37.     void StopCoroutine()
    38.     {
    39.         StopCoroutine(coroutineInstance);
    40.         ((IDisposable)coroutineInstance).Dispose(); // This will make sure the
    41.                                                // finally block gets executed right away!
    42.         coroutineInstance = null;
    43.     }
    44.  
     
    Last edited: Aug 19, 2015
  22. gwiazdorrr

    gwiazdorrr

    Joined:
    Sep 29, 2014
    Posts:
    102
    You don't need to change the return type of your coroutines from System.Collections.IEnumerator to System.Collections.Generic.IEnumerator<object> to get that behaviour. The type compiler generates behind the scenes will implement both System.Collections.IEnumerator and System.IDisposable, as indicated in the first post of this thread.

    I think the way to go here is to write StopAndDisposeCoroutine extension function that calls StopCoroutine and then disposes IEnumerator.
     
  23. JeanSimonet

    JeanSimonet

    Joined:
    Nov 23, 2012
    Posts:
    31
    Oh yeah, you're totally right, I'll update my post!
     
    gwiazdorrr likes this.
  24. Deleted User

    Deleted User

    Guest