Add a cancel method to threads

As a developer, it’s annoying to have to grab the cancel function from a library. It should just be a member function directly on the thread object.

task.cancel(thread)thread:cancel()

9 Likes

This would be great, and a must need. I would honestly love an implementation of a thread library the way it’s done in C#.

thread.new(function: (A...) -> (R...))
thread:start()
thread:cancel()

2 Likes

Is this really necessary? I don’t see anything wrong with the current method

7 Likes

Dude, call the function, it’s 4 extra characters.

Just how often are you cancelling threads? I’m curious what coding pattern led you to feel the need to request this.

27 Likes

i’m assuming you’re canceling a lot of threads and you want to shorten it

local cancel = task.cancel;
...
cancel(thread)

does something like this not work?

4 Likes

I don’t see how this is an improvement to what we already have? I mean, it might take a minute to learn which function to use, but using it takes a second.

also @tnavarts editing their comment to make it more respectful made me lol

2 Likes

I’ll have you know that I edited it because I am actually curious what coding pattern led to this request.

2 Likes

This would be neat I think. It always struck me as odd that strings were the only data types that are indexable for their methods in Lua.

It would be neat if this could be implemented for other data types too… For example numbers using the math library (n:clamp(0, 1) instead of math.clamp(n, 0, 1)) but I don’t know if this is in the scope of your feature request :man_shrugging:

1 Like

Lua has a built in coroutine library that can achieve various things Roblox threads fail to do, like canceling a routine. It’s pretty much the same concept. I prefer to use coroutines over tasks.

It would be very slightly more convenient. I think forwards, so I usually type the variable I want to do something with first, and then I remember I have to use a library and I can’t just call a method off of it. It’s very clumsy to go back and wrap that variable with the library function call, so the whole experience is a little annoying.

It is good for intuition, some people get it confusing when it comes to threads

Why is adding library functions to the datatypes as a namecall becoming a thing? Have people forgotten that string metatables are one of Lua’s worst mistakes?

("").a -- nil, not error
("a").len("bc")
(""):char() -- legal, but wrong types

Library calls are magnitudes faster than any namecall, especially with Lua’s fastcall optimizations. You can even squeeze some performance out by inlining Roblox’s own datatype methods. Namecalls are just inherently slow because of the function indirection required for them to work.

There are also many library functions that don’t map well if you applied them as namecalls. Should thread:create() even be possible?

3 Likes

Wow I’m surprised to find people so strongly opposed to adding a shorthand function. Perhaps my reasoning isn’t extraordinarily compelling, but shouldn’t the bar for this be more like “this makes programming slightly more convenient for some people” instead of “this is literally impossible without the requested feature”? This seems very low stakes.

I use a lot of threads throughout my game. A quick search gives me 52 results across 26 files for “task.cancel” alone. It’s very convenient for managing any kind of latent operation that depends on another state, and especially so when there’s a chain of latent requests that all want to execute in succession. Just scanning through the results, here are some of my use cases.

  • Player joins and i need to check their game passes, badges, etc. If they leave I should cancel the threads making those requests. All data requests count as a sequence of latent requests since I need to repeat them if they fail.
  • Player dies and I play a sequence on their camera involving watching their ragdoll, zooming in, waiting, watching their killer, and eventually transitioning to regular spectate. This can be interrupted if the player exits spectate during the sequence, or the match ends, or several other things happen.
  • Player opens a loot crate and I play a scripted animation sequence similar to the one above. Again, this can be cancelled prematurely by several things. Not all of which are actual player input. There are several sequences like this in the game.
  • I play a scripted animation sequence when the player closes a gui window. This can be interrupted if they try to reopen the window before it finishes closing.
  • I preload all player characters and cache them so that I don’t have to yield when spawning the character. Like loading player data, this needs to be cancellable.
  • I have a connection, like a PreRender connection, that wants to be fired by different events depending on its state during its previous iteration. This is managed by starting a thread with a while loop and waiting on the chosen event directly. It’s cancelled using task.cancel instead of connection:disconnect().
  • I have a great number of gui elements that have prerequisites for starting an operation, such as requiring player data before displaying an item. This is managed by deferring and thread and cancelling it when the gui is destroyed.
  • When an item loads into a viewport I don’t want to display it if it’s not fully loaded, so I execute a sequence of waiting for content and other data before fading the image in. This is cancelled early if the item viewport is destroyed.
  • Some items show timed discount labels with a countdown. This countdown uses a thread with a while loop so that it only executes once per second. It’s cancelled when the gui is destroyed.

I could go on and on about how valuable this function is for managing execution and scripting animation sequences.

This guy gets it. task.cancel(thread) is simply clumsier than thread:cancel() to my eyes.

That’s your opinion, man. I use string metatables exclusively whenever the option is available. Perhaps Roblox should encourage people to program in a way that fits their game and programming style.

That’s fine. I’m not asking that the library functions be removed. You can always use them if you prefer. I don’t think I have a single part of my game that cares about the performance cost of namecalls. You’d really have to get into it with a very dense algorithm to notice that.

That doesn’t make sense to me.

3 Likes

The obvious argument against is that there are things in the task library will likely have in the long run like any / all combinators which aren’t naturally expressed as methods because they operate on multiple tasks rather than a single one.

RE the example cases: Isn’t the hard part of handling said async work mostly the lack of terse error handling primitives though rather than a small number of extra characters in task library syntax? Seems like barking up the wrong tree.

Yeah I mean I don’t think everything should exist as a method by any means. Just things that are obviously operations directly on a task - like cancel.

Honestly that doesn’t bother me as much :grimacing:

1 Like

time is money! time saved is money saved!

i agree with these use cases and task.cancel being extremely powerful but i don’t think saving 0.1 seconds for typing is worth it and i’m an OOP hater anyway :joy: (I used to be OOP fan)

task.cancel is a lot easier to understand than tread:cancel, it is not as intuitive to read (shouldn’t we be optimizing readability?)

is it really that important to support this functionality?

i can understand for Instances with tags and attributes however i personally never had an issue without a shorthand, whatever extra functionality i find missing i’d write my own solution anyway

I’m sorry but not wanting to type the extra word strikes me as you just being lazy

I find that task.cancel is more verbose in what it does rather than thread:cancel. The issue is that anything can implement a cancel method which creates confusion in codebases if its not immidiately clear we’re dealing with a thread. It also feels a little bloatish to add this, is 4 extra characters really a hassle to type?

I get Luau typing can somewhat resolve this but it still requires searching around the codebase looking for the implementor of said method.

also wrt luau, it would hook to coroutone.close not task.cancel for portability sake

task.cancel meanwhile is extremely obvious what it does and what its meant to be used for, as you shouldn’t be naming vars the same things as builtins.

The only primitive datatype in Luau that has methods is strings, and thats because of some metatable hack thats really bad and should’ve never been implemented. You can make the argument that thread isn’t primitive, but personally, it almost definitely is.

1 Like

Wouldn’t this mean setting a metatable on threads (primitive objects), and therefore causing a performance loss?