Add resumeThread global function

resumeThread(t: thread, args: ...any)

The resumeThread (name pending) function behaves similar to coroutine.resume: it receives a thread and a number of arguments, and resumes the thread, passing the arguments to the thread. The difference is that no values are returned, and errors are handled by the engine, in the same way as spawn, delay, events, and so on. I believe that the addition of this function is an essential piece to properly integrating coroutines with the Roblox engine.

Developers generally have several requirements in regards to threads:

  • Immediate: There must be no significant delay between resuming the thread and the thread actually running.
  • Error handling: Errors must be handled by the engine. The engine has several error-monitoring mechanisms that require errors to be properly handled in order to be usable (LogService.MessageOut, ScriptContext.Error, dev console).
  • Inexpensive: Spawning threads shouldn’t require big chunky objects.
  • Arguments: Passing arguments to the thread should be supported.

Let’s enumerate the various ways of spawning threads, and how they meet these requirements:

coroutine.create/resume

  • :white_check_mark: Immediate: thread runs immediately with no delay.
  • :x: No error handling: errors must be handled manually, with no means to pass the error to the engine.
  • :white_check_mark: Inexpensive: the baseline for threads in terms of performance.
  • :white_check_mark: Supports arguments: resume receives arguments to be passed to the thread and returns values returned by the thread.

coroutine.wrap

  • :white_check_mark: Immediate: the function runs immediately with no delay.
  • :x: Poor error handling: errors that occur within the thread are emitted directly, killing the caller of the wrapper.
  • :white_check_mark: Inexpensive: just a function that wraps coroutine.create/resume.
  • :white_check_mark: Supports arguments: wrap receives arguments to be passed to the thread and returns values returned by the thread.

spawn

  • :x: Not immediate: the created thread is resumed after a minimum wait time.
  • :white_check_mark: Proper error handling: errors are handled by the engine.
  • :white_check_mark: Inexpensive: just a function.
  • :x: No argument support: spawn cannot receive arguments. A closure must be used.

delay

  • :x: Not immediate: the created thread is resumed after a specified delay, but this delay has a minimum wait time.
  • :white_check_mark: Proper error handling: errors are handled by the engine.
  • :white_check_mark: Inexpensive: just a function.
  • :x: No argument support: delay cannot receive arguments. A closure must be used.

events

  • :white_check_mark: Immediate: listeners run immediately with no delay.
  • :white_check_mark: Proper error handling: errors are handled by the engine.
  • :x: Expensive: requires creating an instance with extra baggage that has nothing to do with threads.
  • :x: Poor argument support: BindableEvents can pass arguments to listeners, but the values are copied.

Finally, these are compared to the resumeThread function:

resumeThread

  • :white_check_mark: Immediate: thread runs immediately with no delay.
  • :white_check_mark: Correct error handling: errors are handled by the engine.
  • :white_check_mark: Inexpensive: just a function.
  • :white_check_mark: Supports arguments: resumeThread receives arguments to be passed to the thread.
32 Likes

I’d like to additionally point out some caveats of spawn/delay. Both will yield for approximately 1/30th of a second making them pretty useless or at least very difficult to use for any context they’d be needed. As pointed out above, coroutine wrapping emits errors to the calling thread and resuming will not even handle/output errors.

The obvious issue with a simple pcall: No stack tracing. Xpcall can’t yield. Spawn passes unneeded arguments to its callback and doesn’t support passing variables.

Spawn has been the reason for multiple of my scripts completely failing to work or acting extremely slow. For example, if you want to generate terrain for your game and continue setting up the rest of the game you need to settle on spawn or coroutines and you can’t do anything in loops with spawn or you’ll absolutely kill the performance of your code because it’ll all be running at 1/30th offsets from each other.

2 Likes

This is a great request! I avoid using the coroutine features to any degree in “production” code due to the error handling problems.

3 Likes

This is a really great comparison between the different mechanisms we currently have for thread creation/resumption. While I can’t imagine this as a global method, I do see it’s use and think it’s worth looking into.

13 Likes

I like the idea, and I’d like to see it implemented, but would there by any real downside in adding it to the coroutine table as another function as opposed to the global environment?

4 Likes

It looks like this will be implemented in the task.spawn function:

local function f(...)print("f:",...)return"result"end
-- supports arguments
print("results 1:",task.spawn(coroutine.create(f),1,2,3))
print("results 2:",task.spawn(f,4,5,6))
print(7,8,9)
-- runs immediately
--> f: 1 2 3
--> results 1:
--> f: 4 5 6
--> results 2:
--> 7 8 9
task.spawn(function()error"A"end)
-- errors are handled
--> ServerScriptService.Script:1: A
--> Stack Begin
--> Script 'ServerScriptService.Script', Line 1
--> Stack End

This function should also be reasonably inexpensive.

5 Likes

Can confirm. resumeThread has been implemented as task.spawn, which is is analogous to immediate-mode signals. Additionally, there is task.defer, which is analogous to deferred-mode signals. There is also task.delay and task.wait, which are equivalent to delay and wait, but depend on heartbeat instead of the throttled scheduler.

7 Likes

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.