It’s a lot to read so I hope you’re ready haha.
The problem with Roblox’s conventions is that “async” functions have the word “async” because it does asynchronous tasks internally, however, they are not asynchronous on the Lua side. Instead, they actually yield and the function itself is synchronous with your code. For example, GlobalDataStore:SetAsync()
is actually synchronous, and yields until it finishes it’s execution. Unfortunately, we are too late and too far in to change this naming design.
I mention this fact because it is the same principle as submitAsync
. When running the function, it is actually a synchronous operation in it’s current thread, and is only ever “asynchronous” if the current thread itself is asynchronous and you don’t care about anything but adding to the queue.
Here’s an example of what I mean about it’s synchronous behavior:
local threadQueue = ThreadQueue.new()
-- this will yield for 1 second before returning true, "Hello, World!"
local success, response = threadQueue:submitAsync(function()
task.wait(1)
return "Hello, World!"
end)
-- meaning any code after this has to wait until the submitted callback above is finished
print(success, response)
Here’s an example of asynchronous behavior in the context of another asynchronous thread where you don’t particularly care about anything after:
-- process each item with a 1 second delay after,
-- aka rate limiting
local threadQueue = ThreadQueue.new(1)
local someRemote = game:GetService("ReplicatedStorage").someRemote
someRemote.OnServerEvent:Connect(function(player)
threadQueue:submitAsync(function()
print(player.Name)
end)
end)
Concurrency is a different topic. This is how the queue works with concurrency disabled:
- Submit a function through
submitAsync
, gets added into the back of queue.
- If the queue is not running, start the queue for processing.
- If there is an item in the queue, it fires that callback function in a pcall and resumes the stored yielded thread with the values returned by the pcall. The queue has to wait for these steps to complete (synchronous).
- If specified, waits for
timeBetween
seconds.
- Go back to step 3.
With concurrency enabled, the queue does not wait for the callback function to complete. It executes the callback function and resumes the thread in another thread, meaning it goes straight to step 5.
- Submit a function through
submitAsync
, gets added into the back of queue.
- If the queue is not running, start the queue for processing.
- If there is an item in the queue, the queue creates a new thread that fires that callback function in a pcall and resumes the stored yielded thread with the values returned by the pcall. Since it is ran on a separate thread, the queue does not need to wait for these steps to complete (asynchronous).
- If specified, waits for
timeBetween
seconds.
- Go back to step 3.
-- threadQueue with a second delay and concurrency disabled
local threadQueue = ThreadQueue.new(1)
-- threadQueue with a second delay and concurrency enabled
local threadQueueConcurrency = ThreadQueue.new(1, nil, true)
-- examples
local function executeAndPrint(callbackFunction)
task.spawn(function()
print("Non concurrent", threadQueue:submitAsync(callbackFunction))
end)
task.spawn(function()
print("Concurrent", threadQueueConcurrency:submitAsync(callbackFunction))
end)
end
for i = 1, 10 do
executeAndPrint(function()
task.wait(1)
return i
end)
end
--[[
Output:
10:20:16.639 Non concurrent true 1 - Server - DemoScript:12
10:20:16.639 Concurrent true 1 - Server - DemoScript:16
10:20:17.641 Concurrent true 2 - Server - DemoScript:16
10:20:18.645 Non concurrent true 2 - Server - DemoScript:12
10:20:18.645 Concurrent true 3 - Server - DemoScript:16
10:20:19.649 Concurrent true 4 - Server - DemoScript:16
10:20:20.653 Non concurrent true 3 - Server - DemoScript:12
10:20:20.653 Concurrent true 5 - Server - DemoScript:16
10:20:21.656 Concurrent true 6 - Server - DemoScript:16
10:20:22.656 Non concurrent true 4 - Server - DemoScript:12
10:20:22.661 Concurrent true 7 - Server - DemoScript:16
10:20:23.665 Concurrent true 8 - Server - DemoScript:16
...
]]
In both cases, submitAsync
itself is still synchronous, and will only resume and return when the callback function has finished processing. The only difference is the concurrent queue itself doesn’t have to wait for the callback to complete since it’s in a separate thread.
In your example, that would be correct. Since your timeBetween
is not specified, it is defaulted to 0
. Your concurrent thread executes the function without waiting for your task.wait(waitTime)
to complete and moves onto the next item without delay, even if your function isn’t completed.
If you had a timeBetween
, each one would print every timeBetween
after the first item.
If you had a timeBetween
and concurrency
disabled, each one would print every waitTime
+ timeBetween
after the first item.