✓ Accepted answer
In a sentence: use spawn
If your code does not use coroutine.yield, you do not have business using the coroutine library.
Use spawn.
Why
Lua is single-threaded. Only one piece of code runs at a time.
A coroutine can signal to its runner that it is going to take a break using coroutine.yield. ROBLOX uses wait() as the opportunity to yield. This makes the current script stop running. ROBLOX then picks another script and lets it run, until the next wait().
spawn tells ROBLOX about a new function to schedule. It puts it into the queue so that it can be picked later when the current script is done working.
coroutine.resume and coroutine.wrap are a pure Lua concept; they don't actually have to do with different tasks running. Because ROBLOX uses coroutines to implement tasks, you can actually just wait() within a new coroutine, and that coroutine will start being scheduled.
While this does appear to work, it doesn't work when you actually use coroutines as coroutines (because ROBLOX has taken them over). This can get confusing fast; this is why it is easy to recommend spawn: it means what it does and does only what it means.
A minor difference
There is one small practical difference between spawning a new task and explicitly creating a coroutine.
coroutine.resume(coroutine.create(f)) will run f() immediately.
spawn(f) will just queue up f() to be run later.
print("before")
coroutine.wrap(function()
print("now")
wait(1)
print("later")
end)()
print("after")
print("before")
spawn(function()
print("now")
wait(1)
print("later")
end)
print("after")
This has little practical consequence, since if you really need some code to finish before you can move on, you shouldn't be doing it in a different task anyway.