Task.spawn() vs coroutine.resume(). Do they do the same?

What exactly is the difference between these two functions? From my point of view they do exactly the same thing. If for example we have a function (or coroutine to make it simpler):

local co = coroutine.create(function()
	print("A")
end)

Then they both resume it immediately, so they do exactly the same thing.

task.spawn(co)
print("B")
--> A
--> B
coroutine.resume(co)
print("B")
--> A
--> B

The “task” library is an expansion onto coroutine. You should read the documentation on both
task and coroutine.

I already did, but it doesn’t say anything about it, that’s why I ask here.

Im my use cases they do the same thing. I prefer task.spawn() because it is a lot simpler to use and makes my code easier to read at least to me. I’m not exactly sure what the main advantages of corutines are but I mainly prefer task.spawn()

1 Like

Take this with a grain of salt as I haven’t gone terribly in-depth with coroutines, but I’ve just found that task.spawn is basically coroutine.create and coroutine.resume together.

local func = function()
	print('a')
end
task.spawn(func)
coroutine.resume(coroutine.create(func))

But I know with coroutine, you can to some fancy stuff with pausing and resuming it. Though I think you can do that with the task library too?

Either way, I think the task library is the preferred method, where as the older coroutine library is there mainly to support older scripts or perhaps any extra features that the newer task library may lack.

2 Likes

I think the difference is in the way errors are handled. When using task.spawn and the coroutine errors, it’ll error like a normal function. If you use coroutine.resume and the coroutine errors, it just returns it as a boolean saying that it errored and the error after that. You should task.spawn since it will make debugging a lot easier.

7 Likes

One critical difference I have discovered is with what happens when returning inside coroutine.resume vs task.spawn after yielding in contexts such as inside a remote/bindable function invoke.

Lets say you do something like:
Client:

local remoteFunc = ...
print("Invoking server.")
remoteFunc:InvokeServer()
print("Got response.")

Server:

local remoteFunc = ...
remoteFunc.OnServerInvoke = function()
    print("Invoked.")
    local running = coroutine.running()
    task.defer(function() -- obviously could just use the thread as the argument here but this an example
        coroutine.resume(running)
    end) -- runs the next thread cycle (after the following code runs)
    coroutine.yield()
    print("Responding.")
end

When you run this you will get the output:

Client:
Invoking Server.
Server:
Invoked.
Responding.

Notice that the invoke never actually returns to the client, leaving the thread on the client suspended.

However on the server if you use task.spawn:

llocal remoteFunc = ...
remoteFunc.OnServerInvoke = function()
    print("Invoked.")
    local running = coroutine.running()
    task.defer(function()
        task.spawn(running)
    end)
    coroutine.yield()
    print("Responding.")
end

the output will be:

Client:
Invoking Server.
Server:
Invoked.
Responding.
Client:
Got Response.

This implies that if you resume a thread with task.spawn, it will actually tell the invoke to return to the client upon the thread ending. Admittedly, this is an obscure usecase however I have run across it multiple times when trying to mesh the results of multiple async call returns without waiting in sequence, and lean heavily on coroutines. So in short, you should probably always use task.spawn over coroutine.resume.

17 Likes