- Home /
How to stop a co-routine in C# instantly?
I have been looking around for days now and cant seem to find a way to instantly stop a co-routine.
I have a character that performs several different actions, some can take up to 30 secs to complete. Each are different co-routines. I need to stop the action the character is doing instantly then call the next. At the moment one of two things happen, both co-routines run at the same time causing the character stop working, or I have to wait for the co-routine to stop before I can call the next one.
How to stop a co-routine instantly and then start the next.?
I used this code as a test. Its a simple code that counts to 10 in seconds, printing the seconds. Pressing Q runs StopCoroutine. If you press Q within the 10 seconds you can clearly see the co-routine carries on counting in the consol.
using UnityEngine;
using System.Collections;
public class TestCoRoutine : MonoBehaviour {
void Start () {
StartCoroutine (StartCounting());
}
// Press Q to run StopCoroutine
void Update () {
if (Input.GetKey("q")){
StopCoroutine ("StartCounting");
print ("Stopped");
}
}
IEnumerator StartCounting(){
yield return new WaitForSeconds(1);
print ("1 second");
yield return new WaitForSeconds(1);
print ("2 second");
yield return new WaitForSeconds(1);
print ("3 second");
yield return new WaitForSeconds(1);
print ("4 second");
yield return new WaitForSeconds(1);
print ("5 second");
yield return new WaitForSeconds(1);
print ("6 second");
yield return new WaitForSeconds(1);
print ("7 second");
yield return new WaitForSeconds(1);
print ("8 second");
yield return new WaitForSeconds(1);
print ("9 second");
yield return new WaitForSeconds(1);
print ("10 second");
}
}
Thanks
Answer by Bunny83 · May 16, 2015 at 05:00 PM
Since the question got bumped already i'l like to add that the mentioned feature here in the comments has finally been implemented ^^. StopCoroutine now comes in 3 variants:
StopCoroutine(string);
StopCoroutine(IEnumerator);
StopCoroutine(Coroutine);
The string version still has the same limitation. So you can only stop a coroutine with the string version of StopCoroutine which has beed started with the string version of StartCoroutine. The string version has more disadvantages as you're limited with the parameters you can pass and you can only start a coroutine that is actually part of the script.
The IEnumerator version of StopCoroutine has caused a lot of confusion by a lot people. The quirks with the IEnumerator version is that you have to use the same IEnumerator object that you have used to start the coroutine. However each time you invoke your coroutine you will "generate" a new IEnumerator object. So this Doesn't work:
StartCoroutine(MyCoroutine());
StopCoroutine(MyCoroutine()); // doesn't work. It's a different IEnumerator object
To use the IEnumerator version you have to store the IEnumerator object before you pass it to StartCoroutine:
IEnumerator co;
co = MyCoroutine(); // create an IEnumerator object
StartCoroutine(co); // start the coroutine
StopCoroutine(co); // stop it.
The IEnumerator version is actually cumbersome. The Coroutine version is much better:
Coroutine co;
// start the coroutine the usual way but store the Coroutine object that StartCoroutine returns.
co = StartCoroutine(MyCoroutine());
StopCoroutine(co); // stop the coroutine
So if you need to stop a coroutine either use the Coroutine version of StopCoroutine or the string version if you don't need any parameters.
I'd only add that one reason for using the IEnumerator version is if you have multiple instances of the same coroutine, it allows you to selectively stop one of them.
Uhm, but there's no real gain in using the IEnumerator. Each started coroutine has it's own unique Coroutine object which is returned by StartCoroutine. Using the "IEnumerator" has no advantage over using "Coroutine".
// 3 instances of the same coroutine
Coroutine c1 = StartCoroutine($$anonymous$$yCoroutine());
Coroutine c2 = StartCoroutine($$anonymous$$yCoroutine());
Coroutine c3 = StartCoroutine($$anonymous$$yCoroutine());
// only stop c2
StopCoroutine(c2);
I tried that and didn't seem to be getting distinct c1, c2, and c3 values returned back for each started routine - they all seemed to point to the same instance, yet the IEnumerator version worked fine (which, to be fair, I thought was pretty odd!).
I will have to try and strip down my code to create a simple test case but, for now, I'm reluctant to change anything since my somewhat fragile code is working - yay!
Thank it is worked but with corrouine not with Ienumerator. I guess there is not Ienumerator variant exists
What about restarting a stopped coroutine? Is StartCoroutine(co); possible? Is there any difference between:
co = StartCoroutine($$anonymous$$yCoroutine());
StopCoroutine(co);
StartCoroutine(co);
And
co = StartCoroutine($$anonymous$$yCoroutine());
StopCoroutine(co);
co = StartCoroutine($$anonymous$$yCoroutine());
Well, coroutines aren't ment to be restarted or resumed (keep in $$anonymous$$d there's a big difference between the two). There is no version of StartCoroutine which takes a Coroutine object as parameter. Once you stopped a coroutine the "Coroutine" object becomes useless.
However there are ways to kind of resume or restart a coroutine.
First of all restarting usually isn't possible since the IEnumerator objects that the compiler generates does not support the "Reset" method. That's because there's no way for the compiler to know how to reset the internal state into something meaningful. However there is a way by using "IEnumerable" ins$$anonymous$$d of "IEnumerator" and store it for later use:
IEnumerable $$anonymous$$oveTo(Transform aTarget, float aSpeed)
{
while (transform.position != aTarget.position)
{
yield return null;
transform.position = Vector3.$$anonymous$$oveTowards(transform.position, aTarget.position, aSpeed * Time.deltaTime);
}
}
IEnumerable move = $$anonymous$$oveTo(someObject, 5);
// start the coroutine:
Coroutine co = StartCoroutine(move.GetEnumerator());
// stop the coroutine
StopCoroutine(co);
// restart coroutine with the same parameters
StartCoroutine(move.GetEnumerator());
Resu$$anonymous$$g is also possible but not recommended. When you resume a coroutine the yield statement at which the coroutine will be stopped will basically be ignored. This can be a big problem when you wait for a WWW request or something else.
IEnumerator $$anonymous$$yRoutine()
{
Debug,Log("CU in 1 day");
yield return new WaitForSeconds(3600*24); // wait 1 day
Debug.Log("Hello world");
}
IEnumerator co = $$anonymous$$yRoutine();
StartCoroutine(co); // prints "CU in 1 day"
StopCoroutine(co)
StartCoroutine(co); // prints "Hello world"
So here we reuse the same IEnumerator object which is basically resumed when you call StartCoroutine again. However as you can see the 1 day wait time will just vanish since the coroutine got stopped at the yield and will be resumed after that yield.
Thanks, those are really good to know! So let's ignore for now the last one that skips the yield (which is in most case not ok).
Then the only true restarting is
IEnumerable myEnum = $$anonymous$$yEnum();
Coroutine co = StartCoroutine(myEnum.GetEnumerator());
StopCoroutine(co);
StartCoroutine(myEnum.GetEnumerator());
And I guess this would even resume variables with their values at the time it was stopped such as in:
IEnumerable $$anonymous$$yEnum(){
int count = 0;
while (count<9999)
{
yield return Waitforsecond(1);
++count;
}
}
This would resume with count being of whatever value it was at the time that it stopped. So this would be the main advantage over simply starting a new Coroutine of the same type using the same input parameters.
Is there any disadvantage of this method, such as too much of memory footprint or anything while it's being stopped and keeping the parameters stored?
Answer by Kryptos · Aug 13, 2012 at 12:46 PM
As can be read in the documentation, StopCoroutine(string) can only stop coroutines started with StartCoroutine(string). Since you used the other version StartCoroutine(IEnumerator), this does not work.
Two solutions. First use the string version of StartCoroutine:
StartCoroutine("StartCounting");
Second, use a boolean value and check it before any yield statement:
private boolean stopped = false;
IEnumerator StartCounting()
{
stopped = false;
yield return new WaitForSeconds(1);
print ("1 second");
if (stopped)
{
yield break;
}
yield return new WaitForSeconds(1);
print ("2 second");
if (stopped)
{
yield break;
}
yield return new WaitForSeconds(1);
print ("3 second");
// and so on...
}
// or using a loop
IEnumerator StartCounting()
{
int i = 0;
stopped = false;
do
{
yield return new WaitForSeconds(1);
print (i+" second");
i++;
} while(!stopped && i<10);
}
To stop the coroutine, just set the boolean to true.
Thanks, that works perfectly. Cant believe the answer is so simple.
I just remembered I'm sending parameters to the co-routine. How would I do this using the string method?
Check stopped
after each yield. The last function prints one extra after stopped is set false.
You should increment i somewhere ;) You could have used a for loop as well.
IEnumerator StartCounting()
{
stopped = false;
for (int i = 0; !stopped && i < 10; i++)
{
yield return new WaitForSeconds(1);
print (i+" second");
}
}
The count shouldn't start at 0 after you've waited a full second.
Also, assu$$anonymous$$g you fix the above fix, if you set stopped to true 2.5 seconds in, that example will still print "3 second".
Answer by beriss · Aug 13, 2012 at 11:53 AM
You can use StopCoroutine for stop any coroutine. If you want to stop all coroutine you can use StopAllCoroutines.
That's not true unfortunately. You can only stop coroutines with StopCoroutine that have beed started with the string version of Startcoroutine, otherwise there's no relation between the function name and the created IEnumerator that is returned by the function. Unity actually misses a buildin way to stop an arbitrary coroutine. Hopefully they implement it.
StartCoroutine already returns the internal used Coroutine instance it's creating, but you can't do anything with it except using it in a yield statement to wait for it to complete. It would be great if there was a Stop / Ter$$anonymous$$ate method ;)
Btw StopAllCoroutines should really stop all coroutines on this behaviour, but that's not always what you want.
I tried voting but was out of votes. I really could use this feature...WaitForSeconds is problematic without it, and sometimes impossible to use, forcing an unnecessary yield return null just to see if it's been canceled. I'll reiterate the link:
This latest comment by Bunny83 on 16 $$anonymous$$ay 2015 should be promoted to the newest accepted answer since it has the latest information. Your post was immensely helpful, though I don't think I can give it the credit it deserves since it's currently a comment. Thanks nonetheless.
Answer by JDavidL · Sep 19, 2017 at 06:31 AM
This post is a little old but it helped me and i want to contribute synthesizing what finally worked for me with an IEnumerator:
I declared a "private Coroutine co" , then, when i needed to start the coroutine (including yield return new WaitForSeconds) I replaced "co = StartCoroutine (MyCoroutine());" instead of "StartCoroutine (MyCoroutine());" and when i needed to stop "StopCoroutine (co);"
That was all! Works fine!
?? That's the same thing as in Bunny's answer (the green one at the top,) which is more complete.
The trick to understanding this Q (if you read every comment you'll see this) is that there wasn't a useful StopCoroutine function before. The answers from 2012 are the best they could do, at the time. If you see an old Q like this where some new feature makes it easier, it's fine to add the new better way (that's what Bunny did, 3 years after the Q was asked.) But you can save some of your time by reading to see if anyone did that already.
Answer by Technic235 · Sep 23, 2018 at 10:41 AM
I made it so I can't turn or move my character while attacking but it is possible to cancel the attack at any time, making it possible to turn and move before the attack is finished.
public class Player : Character {
[SerializeField] private AnimationClip attackSpearUp;
private bool anchoredAttackRunning = false;
private IEnumerator anchoredAttack;
void Start ()
{
}
void FixedUpdate () {
if (Input.GetMouseButtonDown(0) && anchoredAttackRunning == false)
{
anchoredAttack = anchoredAttackRoutine();
StartCoroutine(anchoredAttack);
}
if (Input.GetMouseButtonDown(1) && anchoredAttackRunning == true)
{
StopCoroutine(anchoredAttack);
anchoredAttackRunning = false;
anim.SetBool("is_attacking", false);
}
if (anchoredAttackRunning == false)
{
Movement();
}
}
private void Movement()
{
//.... blah, blah, blah my irrelevant movement code.
}
private IEnumerator anchoredAttackRoutine()
{
anchoredAttackRunning = true;
anim.SetBool("is_attacking", true);
yield return new WaitForSeconds(attackSpearUp.length);
anchoredAttackRunning = false;
anim.SetBool("is_attacking", false);
}
}
Your answer
Follow this Question
Related Questions
Initialising List array for use in a custom Editor 1 Answer
Multiple Cars not working 1 Answer
Distribute terrain in zones 3 Answers
start method does not start quick enough? 3 Answers
StartCoroutine with string method name from another script? 1 Answer