Add a task function to defer the current thread

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()
3 Likes

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)

1 Like

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.

Example:

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.

1 Like

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.

1 Like

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?

1 Like

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.

1 Like

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.

2 Likes

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
1 Like

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.

2 Likes