Whats the difference from the two, they both make and run new threads right?
There really isnât a huge difference between the two as far as I know.
However, spawn() has a built in wait() into it before the code within it executes. Whereas coroutine executions are instantaneous. To provide an example.
spawn(function()
print("lol")
end)
print("Hello there") --This will print first
---Coroutines---
coroutine.resume(coroutine.create(function()
print("lol") --This will print first
end))
print("Hello there")
However you can use both without any harm. Although if you achieve things by threading. In some cases it will be better to use a coroutine over spawn().
More of, coroutines can be created and not have themselves be called right away.
local a = coroutine.create(function() print("Hello world!") end)
wait(2)
coroutine.resume(a) --Will Print 'Hello world!'
And if youâre to use coroutine.wrap(). You will be able to utilize arguments and parameters as well. Which in certain cases. Can be very useful.
local a = coroutine.wrap(function(str)
print(str)
end)
a("lol") --Will print 'lol' to the output
Error handing with coroutines is a huge pain. I highly recommend using spawn
or delay
to avoid having to deal with this. Alternatively, use @Quentyâs fastSpawn
, which allows spawning immediately, without the delay @Wingz_P mentioned.
The reason for spawn
delaying is that itâs queuing up the function to run the next step in the task scheduler (which is essentially 1 frame). Using Quentyâs fastSpawn uses a fun âhackâ by using a BindableEvent to execute the new thread immediately and get the correct error handling / stack traces.
from the PiL: Programming in Lua : 9.1
coroutine.resume
returns a boolean saying if it was successful and an error message if there was an error, so you could treat coroutines as multithreaded pcalls
Take this coroutine for example:
local co = coroutine.create(function(a)
wait(1)
if invalid.table then
print("error")
end
end)
while true do
local s, msg = coroutine.resume(co,4)
if not s then
error(msg)
end
wait(4)
end
This will error after one second with this message and an appropriate stack trace printed in the console:
attempt to index global âinvalidâ (a nil value)
Therefore, coroutines shouldnât be as much of a pain to handle as a pcall
function should be.
It gets much more complicated when you try to retrieve the actual stacktrace. In large projects, it becomes incredibly difficult to know exactly where errors are coming from. The stacktrace/traceback is incredibly valuable, and is essentially lost with coroutines, unless you do some hacky maneuvers with your code.
Wouldnât coroutine.wrap
be a good alternative? It propagates errors normally, and is easier to use:
coroutine.wrap(f)(args)
You still lose stack info overall though
Yeah, debugging asynchronous state is actually one of the most painful thing so preserving that stack information is really important. Thereâs weird error-ownership by Roblox in certain cases.
I think itâs worth noting that since spawn()
creates a Roblox thread, this means that it will take at least 1/30th of a second before it can run because the task scheduler runs at 30 hz, unfortunately. This means that in general-purpose applications you should avoid spawn()
.
Yup. Spawn() is the same as delay(0).
You can pass the coroutine as the first parameter to debug.traceback. This will get you the full stacktrace to the error.
Iâm not sure about the statement above âdebugging asynchronous stateâ. As lua threads are âcoroutinesâ (not really threads), then as per my understanding race conditions do not exist as no more than one coroutine is executing at once
(assuming appropriate yielding). .
This has the upside of easier to write code (donât have to watch your locks as much), but the downside in poor leveraging of multi-cores where real threads can be moved to. (Ala, Lua Lanes)
I think itâs worth noting that since
spawn()
creates a Roblox thread, this means that it will take at least 1/30th of a second before it can run because the task scheduler runs at 30 hz, unfortunately. This means that in general-purpose applications you should avoidspawn()
.
If you need code to execute immediately, why not just execute it in the current âthreadâ? I would say coroutines / spawns are used for code which can be executed at some time in the near future (or delay, if you want to schedule)
Interesting, Roblox encourages use of coroutine as well -
The fewer scripts using
wait
at any given time, the better.
- Avoid using
while wait() do ⌠end
orwhile true do wait() end
constructs, as these arenât guaranteed to run exactly every frame or gameplay step. Use events likeStepped
orHeartbeat
, as these events strictly adhere to the core task scheduler loop.- Similarly, avoid using
spawn
ordelay
as they use the same internal mechanics aswait
. Uses ofspawn
are generally better served with the coroutine library:coroutine.wrap
andcoroutine.resume
.
Iâm not sure I understand the desire to avoid âspawn()â and âwait()â My guess is the scheduling logic for spawned threads is space/time complex.
Great article, must read - Task Scheduler | Documentation - Roblox Creator Hub
Hereâs a good thread to review, which has Roblox Staff on it.
Except for my game :L And anyone else who does insane number crunching, really. Is there no way to make lua threads be computed like actual threads?
Looks like multicore is on roadmap - Faster Lua VM Released
Weâre also looking into a way to unlock access to multiple cores. As I mentioned during my RDC talk (which youâre all welcome to watch! https://www.rdcglobal.com/video-stream-gallery/lua-as-fast-as-possible-rdc-2019 ), we think we have a design that will allow you to run Lua code on multiple threads safely and performantly, which could unlock performance for some specific usecases that just isnât achievable right now.
Apologies for [probably] necroposting.
If you need code to execute immediately, why not just execute it in the current âthreadâ? I would say coroutines / spawns are used for code which can be executed at some time in the near future (or delay, if you want to schedule)
To take advantage of the ability to run code in parallel. You donât see the parent process halting when you call fork() in C, for example. You might want the parent process to continue working on something while the child process starts and does something else without impeding on the parent processâs progress through its own program.
One example I can give is this radio system I made that lets a player call an artillery strike on a given location. The client can request an artillery strike by asking the server for one via RemoteFunction. On the server, Iâll do a check to make sure the call is valid. If itâs not, then itâll return false, but otherwise, itâll start the code that handles the entirety of the artillery strike and return true. This boolean is used by the client to know whether a call was successfully executed or not, and it is expected to return ASAP as the client has GUI elements and other things to deal with based on what result it gets back.
If I let the main process/âthreadâ handle it all, then the client would not be getting a response from the server until after the artillery strike concludes, because it will have to run all the code for the artillery strike first before being able to return anything to the client. (Obviously, there are workarounds available if the main process absolutely must run the artillery strike code, but those are avoidable here.)
In contrast, if I use a coroutine or spawn(), I can create a child process to handle the execution of the artillery strike while the parent process continues and returns the result to the client [practically] immediately, since in this case itâs not being held up by the burden of running all the code related to the artillery strike.
Another example (although if you end up doing this in an actual project then you might want to rethink what youâre doing) is having a significant number of tasks to complete when a player leaves the game. What happens if the server closes? One thing you could do is have a function bound using BindToClose(), and this function iterates over every player and performs whatever tasks it needs to.
Remember that any functions running while a game tries to shut down has a time limit before the server says âtimes upâ and halts all execution and shuts down. If you have a lot of players in the server and a lot of tasks to do for each player, then letting the main process iterate over all the players and perform all the tasks could mean that some tasks arenât performed for the players at the end of the queue if everything isnât done quickly enough.
If you use a coroutine/spawn(), you could have the main thread iterate over each player and create a new child process that performs the tasks on a given player. The main thread will end quickly since all itâs doing is iterate over a table thatâs at most 100 entries large, and the tasks for each player will be running in parallel, so multiple players will be dealt with simultaneously instead of all of them being handled one by one in a queue.