There’s a strange issue with the behavior of resuming coroutines through module scripts.
One script requires a module, which requires another module. That other module yields the currently running thread until resumed. However, when another script resumes it through the same second module, the thread dies before the first script can continue.
The way I expect it to work is that the first script should finish running as well, but it simply doesn’t. Check it out for yourself: StrangeError.rbxl (55.3 KB)
Update: I tried using task.spawn instead of coroutine.resume to resume the yielded thread, and it seems to be working as expected. It’s good and all but I still need closure as to why this happens.
Could it be that you’re using the same coroutine for both scripts therefore yielding one infinitely? You said you’re using task.spawn now (which starts an entirely new thread) instead of coroutine.resume (which runs an already existing thread)
Could you elaborate on what you mean? I’m pretty sure each script is a separate thread. Also I just checked, calling task.spawn on an already yielded thread resumes it. I believe it only creates a new one when you pass a function instead.
Not too sure about that part, the documentation only says that it “Calls/resumes a function/coroutine immediately through the engine’s scheduler.” but I’ve always known task.spawn to open a new thread but roblox does have the task scheduler for the reason of making threading easier, so it could be that task.spawn handles some specific things by itself that you would otherwise need to do on your own with coroutines.
Edit: The problem could maybe also be the functionality of coroutine.resume itself. task.spawn will completely restart a thread by itself as far as I’m aware but im not too sure if it’s the same for coroutine.resume
Because a module script runs on a separate thread, yes, it sounds very odd, but it’s very true; it has a separate thread until it finishes initiation and finally sends a return.
That makes a lot of sense, but how come all of the threads resume only with task.spawn and task.defer and not coroutine.resume? The thread status turns into “dead” at the end by calling the latter.
coroutine.resume can only resume any not dead thread.
task.spawn can both create and resume a thread; it seems like it creates a new thread if the one you pass is dead, interesting.
The thing is that coroutine.resume will only run the first module’s thread without continuing to the original requiring script’s thread. Once the module thread is done, it will set its status to “dead” and completely ignore the requiring script, leaving it stuck yielded. task.spawn actually does exactly what coroutine.resume is supposed to do without creating a new thread…
Script 1 → Require module A → Require module B → Call function RunFunction and yield
Script 2 → Require module B → Call function SetObject and resume Script 1’s thread
Once the thread is resumed, only module A will run, and die on the return. Script 1 won’t proceed.
Check out the example place I provided in the post, it’s weird.
I believe task.spawn is basically an extension/wrapper for coroutine.resume and coroutine.create. It essentially just simplifies and fixes a few things at a (very) small performance cost.
Because coroutine.resume uses the built-in coroutine library and doesn’t have any notion of the engine’s scripts and how they interact with ModuleScript threads. It’ll just resume execution to the caller when the thread yields/dies, ignoring that the requiring script is also yielding. The response value in your example is not an error but the value the thread returns.
task.spawn doesn’t use the coroutine library, but instead enqueues the thread for the engine’s task scheduler to resume. On the next scheduler cycle, the engine resumes the modules’s thread and also traces its ‘parent’ thread back to resume.
task.spawn also doesn’t return a second value, so response gets assigned nil.
I couldn’t find any official documentation talking about this, but this bug report is relevant.
I may have used poor wording with the term ‘parent thread’. What I really meant was that the thread that is scheduled to resume is the one that gets the closing thread’s return values.
When you use coroutine.resume, it makes the thread that called it be the one that resumes when the passed-in thread ends, ignoring the requiring script’s thread. This doesn’t happen with task.spawn because it doesn’t return any values and still sees the requiring script as the one to resume control to.