Are there any situations where create/resume would be better than wrap? Ever since I migrated from spawn to coroutines I’ve only ever found wrap useful.
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.
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:
-
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, untilcoroutine.resume()
'd again, you talked about it theoretically rather than giving examples. Or howcoroutine.yield()
gets any additional passed arguments whencoroutine.resume()
is called, and actually returns them, or the fact that you can usereturn
inside of a coroutine to actually return something. Consider adding these! -
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.
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!
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.
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.
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)
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.
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.
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.
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.
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.
Yet another brilliant tutorial by ReturnedTrue!
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.
This has helped a lot! Used to never understand coroutines, now I do!
Can someone explain to me how and when this function is ran?