1

Im trying to start and end a coroutine with a button. I can start the coroutine but I cant stop it, if I click the button again after the first time starting the coroutine it just restarts again and the slider value goes up.

heres my code

    public void LoopButton(){

    if (lb == 1){
        StopCoroutine (AutoLoop());
        tb--;
    } else {
        StartCoroutine (AutoLoop ());
        tb++;
    }
}

IEnumerator AutoLoop(){

    slider.value = slider.minValue;

    while(slider.value < slider.maxValue){
        slider.value++;
        yield return new WaitForSeconds(0.5f);
    }

    StartCoroutine (AutoLoop());
}
Bosanac95
  • 75
  • 12
  • 3
    You should propably add a "Canceled" Boolean, wich you can check in the whiles condition. Asuming there is not a dedicated way to deal with this in Unity. It does kind of look like some implicit multitasking is happening here. – Christopher May 02 '18 at 07:14
  • @Christopher Agreed. Note the multitasking via coroutines are just a fancy scheduler that runs all in the same main thread in Unity :) –  May 02 '18 at 07:18
  • @Christopher Awesome, thanks guys! :) – Bosanac95 May 02 '18 at 07:29
  • @Christopher maybe add that as an answer. I'll vote for you :) –  May 02 '18 at 07:59
  • I think there is a Unity-specific way to deal with this so I've added an answer. @Christopher I've also included your suggestion as an alternate answer in my post. – sonnyb May 02 '18 at 12:06
  • Possible duplicate of [Unity stop and start coroutines](https://stackoverflow.com/questions/35735319/unity-stop-and-start-coroutines) – AresCaelum May 02 '18 at 12:12

3 Answers3

5

You need to call StopCoroutine with a reference to the same Coroutine returned by StartCoroutine, like this:

private Coroutine loopCoroutine;

public void LoopButton()
{
    if (lb == 1)
    {
        StopCoroutine(loopCoroutine);
        tb--;
    }
    else
    {
        loopCoroutine = StartCoroutine(AutoLoop());
        tb++;
    }
}

To use this approach, change your AutoLoop method to use a while loop rather than a starting another AutoLoop coroutine at the end of the method. Otherwise, you will not be able to stop this new coroutine that is started at the end of AutoLoop.

IEnumerator AutoLoop()
{
    while(true)
    {
        slider.value = slider.minValue;

        while (slider.value < slider.maxValue)
        {
            slider.value++;
            yield return new WaitForSeconds(0.5f);
        }
    }
}

For an alternate solution, as another user commented, it's also possible to stop the coroutine via a boolean flag:

private bool stopLoop;

public void LoopButton()
{
    if (lb == 1)
    {
        stopLoop = true;
        tb--;
    }
    else
    {
        stopLoop = false;
        StartCoroutine (AutoLoop ());
        tb++;
    }
}

IEnumerator AutoLoop()
{
    slider.value = slider.minValue;

    while (slider.value < slider.maxValue && !stopLoop)
    {
        slider.value++;
        yield return new WaitForSeconds(0.5f);
    }

    if (!stopLoop)
    {
        StartCoroutine(AutoLoop());
    }
}

However, using Unity's StopCoroutine is preferable to using a boolean flag for readability & cleanliness.

sonnyb
  • 3,194
  • 2
  • 32
  • 42
  • 1
    My understanding is that using the boolean flag allows the coroutine to do any sort of clean-up it may require, for example if file access needs to be closed. – Immersive May 02 '18 at 12:27
  • 1
    @Immersive: that kind of Cleanup should be left to a finally block, one way or the other. And finallies should even run on a "ThreadAborted" Exception. This side of sabotage, they should always run. Here are two articles on the mater that I link often: http://blogs.msdn.com/b/ericlippert/archive/2008/09/10/vexing-exceptions.aspx | http://www.codeproject.com/Articles/9538/Exception-Handling-Best-Practices-in-NET – Christopher May 02 '18 at 15:09
  • 1
    @Christopher I agree in principle but it's a bit trickier to do in Unity, particularly for Unity Coroutines because they run on the main thread and not on a new thread (i.e. there is no `ThreadAbortedException`). I'm not familiar with this issue in detail, but I think there are some gotchas to consider when using try/finally with `StopCoroutine`. For example, see [this Unity forum post: "finally block not executing in a stopped coroutine"](https://forum.unity.com/threads/finally-block-not-executing-in-a-stopped-coroutine.320611/) – sonnyb May 02 '18 at 15:29
  • 1
    @sonny: Oh, I had only feared that they could have done something that stupid. The core idea with having the compiler do Cooperative Multitasking for you was that it would not do something that stupid. Looks like the "StopAndDispose()" extension method is the way to deal with that case. – Christopher May 03 '18 at 04:30
0

You can use StopCoroutine.

More info here: https://docs.unity3d.com/ScriptReference/MonoBehaviour.StopCoroutine.html

JackMini36
  • 621
  • 1
  • 5
  • 14
0

Use string for coroutine name. Like this:

public void LoopButton(){

    if (lb == 1){
        StopCoroutine ("AutoLoop");
        tb--;
    } else {
        StartCoroutine ("AutoLoop");
        tb++;
    }
}

IEnumerator AutoLoop(){

    slider.value = slider.minValue;

    while(slider.value < slider.maxValue){
        slider.value++;
        yield return new WaitForSeconds(0.5f);
    }

    StartCoroutine ("AutoLoop");
}