Home > Software engineering >  Stopping a Coroutine started in an IEnumerator is preventing execution of remainder of IEnumerator?
Stopping a Coroutine started in an IEnumerator is preventing execution of remainder of IEnumerator?

Time:12-20

I'm trying to ensure that a certain function, FinalizeVisual() runs after a couroutine finishes. If I let the couroutine finish naturally it works fine. If I call StopCoroutine on the coroutine value that is passed back out via the dictionary the 2nd half of the function after the yield return does not trigger. I would think that stopping the first internal coroutine would cause it to then continue through the rest of the block since I'm not stopping the parent coroutine?

public IEnumerator StartVisualCommandCoroutine(Dictionary<IVisualCommand, Coroutine> dictionary)
{
    Coroutine c = _asyncProcessor.StartCoroutine(_visualCommand.PlayVisualCommand());
    dictionary.Add(_visualCommand, c); //a janky way to pass the coroutine itself back so we can StopCoroutine it if we want it to end prematurely
    yield return c;
    _visualCommand.FinalizeVisual(); //this runs if the coroutine ends naturally, but not if we called StopCoroutine on c
 }

CodePudding user response:

You're trying to execute your command after yield return call, this won't work and usually the IDE should warn you about unreachable code part. Just move the _visualCommand.FinalizeVisual() one line higher.

P.S.: In general the way you're using coroutines here is very-very perfrormance inefficient (as they are in general), try explore UniTask for your needs, this is great async framework package for Unity, will make your life much easier. If you don't want to mess with the package, there is a bunch of tutorials on how to use async/await (i.e. https://www.youtube.com/watch?v=WY-mk-ZGAq8).

CodePudding user response:

I can only guess but I think that it will continue to yield since the Routine you interrupted never actually finished.

In general I would suggest to use the same technique you would usually use in real async code and go for a CancellationToken like e.g.

public IEnumerator StartVisualCommandCoroutine(Dictionary<IVisualCommand, CancellationTokenSource> dictionary)
{
    var tokenSource = new CancellationTokenSource();
    dictionary.Add(_visualCommand, tokenSource);
    yield return _visualCommand.PlayVisualCommand(tokenSource.Token);
    _visualCommand.FinalizeVisual();
 }

Then in order to stop a certain routine you do

yourDictionary[someKey].Cancel();

anda now instead of dirty killing a routine you cleanly terminate it whenever you are yielding. E.g. convert a

yield return new WaitForSeconds(3f);

into a

for(var timePassed = 0f; timePassed < 3f; timePassed  = Tome.deltaTime) 
{
    if(cancellationToken.IsCancellationRequested) yield break;

    yield return null;
}

if(cancellationToken.IsCancellationRequested) yield break;

this way you have full control what should be still executed in case of a clean cancellation of the routine.


I would agree though with Xander's recommendations and also suggest you check out UniTask, it is trivial to integrate and offers great async/awaitable wrappers for both asynchronous and synchronous operations and in your case e.g. would also allow the usage of try - catch - finally blocks

  • Related