Coroutines - When and how to use them

As I said previously, resume can return the boolean if it succeeded.
This can be helpful for debugging your coroutined functions, as wrap only supplies the error string.

3 Likes

This is a pretty good tutorial! It gives a great general knowledge of coroutines, and additional info that might be helpful, such as the notion of a thread. Two notes though, that I hope are considered constructive:

  1. Since this article is all about coroutines, which is one subject in the see of many subjects, it’s considered good work to cover EVERYTHING about coroutines. You did a splendid job with that, although there might be bits and pieces missing. For example, you could’ve mentioned how coroutines are used as iterator functions, as the pil states. Also, it seems that you didn’t cover a lot of stuff about coroutine.yield(), how it yields the coroutine, until coroutine.resume()'d again, you talked about it theoretically rather than giving examples. Or how coroutine.yield() gets any additional passed arguments when coroutine.resume() is called, and actually returns them, or the fact that you can use return inside of a coroutine to actually return something. Consider adding these!

  2. The code examples you give in this article are pretty good, because they’re unusual in a way, and not commonly seen. That’s good, but at the same time might be a problem for beginners. For example:

local Thread = coroutine.create(print);
print(Thread); --// thread: <hexadeciamal memory adress>
coroutine.resume(Thread, "Hello, World!") --// Hello, World!

When you pass print as an argument, that might end up being confusing for some people, so you may wanna explicitly explain it to them. It would’ve been nicer if you introduced print as a second example, rather than a first, so it can be easily understood later after understanding how normal functions are passed as arguments.

10 Likes

Thanks so much for the tutorial. It covers the necessary things to start working with coroutines, and that’s a good thing since a lot of users have trouble using them. Thanks!

3 Likes

Thanks for the feedback and I agree with your points, I’ve added an example of using yield and coroutines as an iterator.

Also, I can see what you mean as most people will see print called and not passed like a function - to replace that I’ve used a function which prints for now.

2 Likes

Overall nice tutorial though I feel you should add that coroutines obliterate your stack traces (from my knowledge, feel free to correct me). You can still fetch them though.

A little mistake here:

local Thread = coroutine.wrap(Write);
print(Thread); --// thread: <hexadeciamal memory adress>
Thread(“Hello, World!”) --// Hello, World!

This doesn’t print thread and its address, but the function and its address.

1 Like

The returns of coroutine.yield, coroutine.resume, and the results of calling the function returned by coroutine.wrap aren’t mentioned.

When a coroutine is resumed with coroutine.resume all extra arguments are either arguments to the thread’s function (if the thread hasn’t started), or results to coroutine.yield. The returns are either false and the error object (if the thread errors), or true and all returns from the thread’s function (if it returned) or all arguments to coroutine.yield.

The function returned by coroutine.wrap works similarly, all arguments to the function are either arguments to the thread’s function (if the thread hasn’t started), or results to coroutine.yield. The results are either an error (if the thread errors) or all returns from the thread’s function (if it returned) or all arguments to coroutine.yield.

If a thread yields with coroutine.yield, the next time the thread is resumed it returns from the yield with all arguments from coroutine.resume returned from the yield. All arguments to coroutine.resume are returns for coroutine.resume.

coroutine.running returns nil if the thread is the main thread (in 5.1), not sure what you mean by if there isn't any (thread object). In any case, you shouldn’t have access to the main thread in roblox.

I think a better description is a running thread which has resumed another coroutine which is currently running, not sure what stop yielding means.

I think this should be phrased as returned from the function, reached the end of the function seems ambiguous.

When the thread is resumed the next time, it returns from coroutine.yield and continues, resulting in the thread ending. (and there is a missing ) too)

local Thread = coroutine.create(function(Number)
    while true do
        print(Number)
        Number = Number+coroutine.yield(Number)
    end
end)
coroutine.resume(Thread,1) --> 1
coroutine.resume(Thread,2) --> 3
local Success,Result = coroutine.resume(Thread,5) --> 8
print(Success) --> true
print(Result) --> 8

coroutine.wrap propagates any errors in the thread.
main thread would be more accurately described as the thread resuming the other thread, since something other than the main thread can resume other threads.

There is also the function coroutine.isyieldable, which returns whether the running thread can yield.

Also worth mentioning, each thread gets its own stack, so debug.traceback should be used with the thread argument to get the stack of another thread. (can be used to get stack info after the thread errors)

7 Likes

Hi I remember reading maybe a year ago that the spawn, delay functions would always at least wait() before being ran and that they couldn’t fire every frame like stepped, heartbeat or renderStepped. I never liked wait() because I see it unwieldy and was wondering if this changed or I made a mistake because here there is no mention of limitations, even on the recent learn Roblox wiki it states coroutines behave with no more restriction than as if you added an in game script for the task.

yeah spawn and delay do both wait() for the next tick/frame, and thats one of the big differences between spawn/delay and coroutines: coroutines don’t wait() while spawn and delay do.

2 Likes

Those are completely different things and cannot be compared.

pcalls are used for debugging [catching errors] , while coroutines are used for creating multiple threads that can run collaboratively.

2 Likes

Despite being able to debug Coroutines with the return values of resume/wrap, I would recommend to use pcalls for any functions that could error. As @UniversalScripter stated, Coroutines’ task is to create new collaborative threads and not handle errors like pcalls. In terms of efficiency, pcalls are much better to use.

2 Likes

Sorry for bringing this back, I was wondering about whether there can be potential memory leaks with using Coroutines? When they’re done, are they GC’d, or would I have to set the coroutine variable to nil? I can’t find information about that anywhere, wouldn’t want to find out the hard way…

Once the Coroutine is completely finished, everything within the Coroutined function is garbage collected like any other.

For the variable, once the thread has left the scope it’s defined within it’ll be garbage collected.

If you define it in the global scope, it may not be GC’d until it reaches EOF. You might want to use do blocks:

do
    --// Code
end

My answers assume you are defining it locally.

2 Likes

If it’s defined, isn’t it the same as variables and functions? It wouldn’t be GC’d if it’s in the global scope, but it doesn’t actually execute the function infinitely, unless I call it, right? It doesn’t actually run the thread, so, just defining it shouldn’t cause a memory leak, right?

The coroutined function is only ran when the returned function from coroutine.wrap is called or the thread returned from coroutine.create is resumed. No memory leaks could occur here.

3 Likes

Yet another brilliant tutorial by ReturnedTrue! :laughing:

2 Likes

I’d like to refer to an excellent video that demonstrates that concurrency is not parallelism. If either of those terms mean something to you, watching the video will help clear up whether coroutines (either in Lua or Go) are truly designed for parallelism. Note that the video refers to Go, but the concepts apply to Lua too.

1 Like

This has helped a lot! Used to never understand coroutines, now I do!

1 Like

Can someone explain to me how and when this function is ran?

I hope there was more people like you doing this kind of tutorials with such a good explanation