A more flexible coroutine interface

I’ve often wanted to do things with coroutines that Unity’s API does not explicitly provide an interface for. Namely:

  • Get a handle to a specific coroutine, with which I can:

  • Pause and unpause the coroutine

  • Stop the coroutine

  • Ask if the coroutine has terminated

  • Start a coroutine from library code that is not directly aware of any MonoBehaviour instance

  • Receive notification whenever a coroutine terminates

  • Not write code to do these things every time I need them!

I’ve also recently had a discussion with someone else who was interested in these same sort of features, but who lacked the ability to implement them. So I present a simple TaskManager script that makes this all much simpler. The source is here:

https://raw.github.com/krockot/Unity-TaskManager/master/TaskManager.cs

The comments document usage.

In a nutshell, this allows you to - with no initialization and from anywhere in any code - do things like:

// Equivalent to StartCoroutine(SomeCoroutine())
new Task(SomeCoroutine());

// Equivalent to the above, but keeps a handle to the running coroutine
Task t = new Task(SomeCoroutine());

// Pause the coroutine at next yield
t.Pause();

// Resume it
t.Unpause();

// Terminate it
t.Stop();

// Test if it's still running.
if(t.Running) {}

// Test if it's paused.
if(t.Paused) {}

// Receive notification when it terminates.
t.Finished += delegate(bool manual) {
    if(manual)
        Debug.Log("t was stopped manually.");
    else
        Debug.Log("t completed execution normally.");
};

I hope someone finds this to be useful. Cheers!

37 Likes

Thank You

looks handy :sunglasses:

Nice work. I’ve been frustrated with the lack of flexibility in coroutines before. This looks like a nice addition to my library. Cheers.

I was only just crying into my coffee last night about being unable to pause/resume coroutines! I thankyou sir, and so does the taste of my coffee! <3

2 Likes

Very nice share, thanks :slight_smile:

Though I’m unsure to what degree its really usefull cause coroutines still run in the main thread so why would i really want to pause them, its not that you would do longer term stuff in them if you ever want to debug your code again, get it working right and leave any cpu time to anything else :wink:

The real gain to me is the explicit stop from a handle :slight_smile: thats the major pain we all had as unity coroutines lack on that end as not even string handles help, it required to use Invoke which is troublesome in its own way

Thank you for this! I’m running a data visualization using unity and am pulling data with WWW calls from multiple servers in parallel, and this really makes my job easier. If you put this in the asset store (and sent me an email reminder) I would gladly pay money for this.

As someone with a non-programming background, forgive the naivety, but as an academic exercise would it be possible to take this code a step further and write it as an extension for the (useless) Coroutine class? Extension is just a static class with carefully defined methods, right? Could you add static variables to that class to mimic things like Running that the extended Coroutine would have access to? Or write them as methods (bool isRunning())?

See no use in extending coroutine as coroutine is just a dummy container.
the “is it running or not” is a thing that the managing host class etc in the end defines (here task) as its responsible for making the coroutine run at all. a coroutine can’t manage itself, it can not even exist on its own (it only exists in context of MonoBehaviour so if I were to add such capabilities somewhere it would be an extension to monobehaviour)

Thanks for sharing, I learned a good deal from the source code. Thanks again for not charging 15 dollars on the Asset store for this as seems to be the trend

Hi! Well done! May I use this in my Asset Store package?

Thank you for sharing that, I always use that for tweening things (because it’s look handy).

FYI, i have some better improvement from your code (i did a fork to your code :wink: ), take a look on here:
https://github.com/willlandmubarok/Unity-TaskManager

it’s about a coroutine system which uses delegates (more simple and powerful), so you can …

void Start() {
    //Tweens from (0,0,0) to (1,0,0) for 20 seconds
    Task.Get(delegate(float t) { transform.position = new Vector3(t,0,0); }, 20);
}

Great! Thanks for sharing :slight_smile:

How would you tell when the coroutine is finished?

You put a command at the end of the coroutine which tells you that it has run and ended. Like you call a function or something.

As a hobbyist, this little module is just what I was looking for. Simple enough to integrate into my repertoire; one class that’s easily integrated instead of a complex, sprawling suite with more functionality than I need.

Put simply, this is awesome. Gives much more flexibility with Coroutines, especially when using singletons. Kudos for it!

Thank you

I am currently trying to add a Restart() method to the task manager, but I ran into a problem. To reuse the Enumerator I have to Reset it by calling Enumerator.Reset(). When I try to do that, I get a NotSupportedException in console.
Any ideas how to get this to work?

Here is what I have so far.

This does not work since iterators which are created by the compiler due to the yield keyword do not have any Reset method since there is no sensible way how that could be pulled off.

So what you want to do is not possible with an IEnumerator. However you could use the IEnumerable interface instead so you can simply create a new IEnumerator when necessary.

If you want to know more about IEnumerators, IEnumerables, coroutines and the yield keyword, have a look at my coroutine crash course that should clear up most of the confusion people have about coroutines and iterators.

@Bunny83 Thanks for the answer. I got it to work now by passing the IEnumerator method as a Func to my wrapper class and recreate the IEnumerator object each time the coroutine starts.

If someone is interested I setup a repository for my approach.