As a developer, it is annoying to defer the current thread.
task.defer(task.spawn, coroutine.running())
coroutine.yield()
I want to do this with one function.
task.yield()
As a developer, it is annoying to defer the current thread.
task.defer(task.spawn, coroutine.running())
coroutine.yield()
I want to do this with one function.
task.yield()
What are the use cases for task.yield()
? Canât you just defer the following code with an annoymous function?
yeah I feel like this is a really poor Feature Request post, it just poses a problem with no examples as to why itâs even needed in the first place (which is what provides the motivation to support/implement it in the first place).
I myself cannot possibly imagine the usefulness of even having to do this in the first place (personally, I would imagine that if you get to the point of having to do this that you need to redesign whatever system relies on it)
Just create a new one? It makes literally no sense to delay an entire script by one resumption cycle.
Use task.wait(), it pauses the coroutine and resumes it on the next Heartbeat. Just like the code you have right there. What I would want is to set the current coroutine to yield and automatically resume another one of my choice without waiting until the next frame.
I have experienced this kind of issue where I have wanted to achieve something, but despite existing functionalities allowing us to do them, we discourage using them just because of their âintendedâ function.
math.clamp(Number, 3, math.huge)
math.max(Number, 3) â Could have done this, yet I didnât until last week lol.
Yes but then youâre starting a new function instead of yielding the current one.
I want to set an attribute, then wait for anything listening to the attributeb to execute, then resume. This is a very common pattern.
Why does that not make sense? I have code on line 50 that I want to run now, and I have code on line 100 that I want to defer. I should be able to manage this task directly within the main scope, just like you would with something like task.wait.
Thatâs not what my code does.
I would argue that the system isnât necessarily designed optimally in that case given that itâs not a common pattern at all (no matter how complex the project is).
I also want to add that you are needlessly calling spawn with defer (spawn and defer both accept threads as arguments), you can simplify it to:
task.defer(coroutine.running())
coroutine.yield()
This isnât to counteract the feature request but given that is how you wrote the function initially you may be using it with spawn elsewhere (which isnât needed).
I still believe that this is an odd request with an unusual/unique use-case and canât see any practicality.
Iâm fully on board with this idea, and I find it particularly useful in practical scenarios. Let me illustrate with a straightforward example:
local someValue
local function getValue(): any?
task.yield()
return someValue
end
-- Resume getValue at a later time when a value is assigned to someValue
someValue = 1234
In this example, Iâm showcasing the desire for a function (or thread) to yield until a later time with a return value.
Another use case involves acquiring a playerâs character:
local function getCharacterOfPlayer(player: Player): Model?
local character = player.Character or player.CharacterAdded:Wait()
local head = character:WaitForChild("Head")
local humanoid = character:WaitForChild("Humanoid")
local rootPart = character:WaitForChild("HumanoidRootPart")
return character
end
Here, an opportunity to use task.yield
arises. I would appreciate having more control over the thread, introducing a bit more complexity to allow the function to yield and potentially cancel itself. For instance, if a player leaves the game, the function may never return. Utilizing task.yield
would enable the function to yield until the playerâs character is added, resume itself at a later time, or even return nil
if the player has left the game.
Considering the task library as an evolved coroutine library, incorporating task.yield
seems a logical extension. It would enhance flexibility and streamline handling scenarios like these without the need for additional libraries like Promise.
I feel like yielding a coroutine until an event fires would break (or be very fragile otherwise) under the new deferred events mode. Are you sure this is a good idea?
OP did not elaborate, so I will. What happens when task.defer
is called, is the thread is added to a queue, where it is resumed during the next active resumption cycle. Since the resumption cycle remains active until this queue is emptied, a chain of task.defer
calls, will all resume sequentially at the same point in the frameâeffectively preventing the cycle from progressing. Because of this, thereâs actually a âre-entrancyâ limit in place, which prevents such potentially unintentional recursions from freezing the engine.
In any case, I would consider the proposed feature API bloat; Iâll never understand why developers now-days seem to be allergic to writing more than one line. Itâs also worth noting that task.defer
can be called directly on the existing threadâwrapping task.spawn
seems an unnecessary abstraction.
Please never write code like this. If a value is not defined but will be later, you shouldnât be trying to access it before it is assigned. This is a prime vector for race conditions.
I donât understand how this feature would help youâre second snippet, either. Again, writing code that yields for the right conditions will just make the rest of your codebase reliant on flimsy timing.
I understand what you are saying, well can you suggest a better solution because this is literally built into Roblox default behavior, you can access Player.Character even before it gets added to workspace and all of the bodypart is loaded in and this can cause problems
so if you have a solution for that let me know because i donât want to write code like that either but i donât feel like got a choice
itâs okay if you donât agree or need the feature but if you donât have a better solution itâs best to keep it to yourself otherwise
Thank you for responding! I tried calling task.defer two times on the same coroutine and got this warning: task.defer should not be called on a thread that is already âdeferredâ in the task library.
Is this what the owner of this post want to avoid and have an alternative method that allows them to defer a task after the coroutine has yielded?
That warning is just the schedulerâs way of telling you that it isnât done with the thread yet. A similar variant of the warning can be produced using any of the task libraryâs functions. Consider the following:
local CurrentThread = coroutine.running()
task.defer(task.spawn, CurrentThread)
task.wait()
Which produces the warning: âtask.spawn should not be called on a thread that is already âwaitingâ in the task libraryâ.
If you want to see the behavior I was speaking of, youâll have to yield:
local OutputMake = {}
task.spawn(function()
task.wait() -- Does not resume until the next frame
print(table.concat(OutputMake, ", ")) -- Outputs 1 through 10.
end)
do
local Thread = coroutine.running()
for Iteration = 1, 10 do -- Re-entrancy error is thrown at 80 iterations
task.defer(Thread)
coroutine.yield() -- All 10 iterations are resumed in the same frame
table.insert(OutputMake, Iteration)
end
end
Thereâs a difference between using scoped variables prior to them being defined and a property on a class or child of an instance potentially being nil. The former can be statically fixed to be consistent in its result while the latter is dynamic and so you have to implement measures to account for that dynamism.
You donât go into any detail on how task.yield would even benefit your character situation; and even worse, it wouldnât do anything anyways.
Task.yield in this situation is being pitched as a way to yield the thread into the next task cycle (which Iâm just now realizing - and surprised that no one else brought it up - task.yield() is a terrible name for this and makes no sense especially when compared to coroutine.yield()).
In context of your âGetCharacterOfPlayerâ function this would serve of no benefit to you, there is no written guarantee that any of these instances or properties exist by the next task cycle. A better solution is to first place this type of function in an area where your whole game can use it to decrease repeated code and then rely on initial checks or fallback to events (Check if the character is fully added, then for every stage that failed listen to events until you can determine that itâs fully added).
Also to note - in this type of function - you shouldnât be using CharacterAdded:Wait(); only in places where you absolutely need the character should you do this (which there are limited cases, which is why that part should be optional, otherwise it should be Model? with no guarantee).
All the proposed use-cases for this addition to the task library have all been either poor examples or systems with poor designs that could be redesigned in a more determined and efficient way. Look at how you could implement something in a better way first, then if there really isnât a better way, make the feature request.