How to instantly kill a thread?

I’m working with a disco light system. I want to be able to start a disco effect – say, lights changing colors – and stop it at any time. Also, effects must be able to run in parallel. In a module, I might have something like this:

local Module = {}

function Module.Start()
	spawn(function()
		while true do
			-- set light color
			RunService.Heartbeat:Wait()
		end
	end)
end

function Module.Stop()
	-- stop the function wherever it is in the current frame
end

return Module

I could throw in some variable Running and check for Running to be true every repetition of the loop. If it’s false, break the loop. But that could quickly get messy with a more complicated disco effect.

How could I do this? Is this a job for coroutines? (They’ve always gone over my head.)

I mean technically speaking, even tho you may assume that a variable would get messy with a more complicated system. I still recommend just

local Module = {}
Module.Running = false

function Module.Start()
    Module.Running = true
	spawn(function()
		while Module.Running do
			-- set light color
			RunService.Heartbeat:Wait()
		end
	end)
end

function Module.Stop()
    Module.Running = false
	-- stop the function wherever it is in the current frame
end

return Module

As coroutines may have a yield, I don’t see an actual way of killing a coroutine, but I don’t think they’re the best route for this type of system.

This is a little dependent on the situation. There is no coroutine.kill, but I don’t think coroutines are your solution here anyway. A much better approach to this particular example would be something along these lines:

local Module = {}

local RunService = game:GetService("RunService")
local Connection = nil

function Module.Start()
    -- check not already running
    Connection = RunService.Heartbeat:Connect(function(dt)
        -- set light color
    end)
end

function Module.Stop()
    -- check running
    Connection:Disconnect()
    Connection = nil
end

return Module

Some more info on why you may wish to avoid using spawn here.

10 Likes

@sircfenner thank you for the little input about spawn, I hadn’t seen that thread.

Darn, a coroutine.kill would be nice. I’ll certainly take both of your posts into consideration for each of the effects individually.

To add a little more, what I’m essentially trying to do is replicate the functionality of disabling/enabling a script. Given this script

while true do
	print("a")
	wait()
	print("b")
	wait()
end

I could enable/disable this script at will and it might print “a” last or it might print “b” last depending on when I disable it. Is there no general implementation to do the same thing in lua? Or do I have to use some sort of Module.Running or case-specific implementation like you’ve shown?

You should take the “co” in coroutine to mean “cooperative”. As such, you can’t just forever terminate a coroutine from the outside. The code in the coroutine and the code wanting to terminate it have to cooperate on deciding a time for the coroutine to “terminate”.

Since the Lua thread scheduler isn’t exposed the easiest way to do this is have a variable or other piece of state that both look at, and then when that state gets set the coroutine breaks whatever loops it’s in.

5 Likes

You can’t instantly kill a thread, the best you can do is to yield a coroutine and then delete all references to that coroutine once you decided you don’t need it anymore. Lua’s garbage collection will collect it once it notices that it isn’t active anymore and that’s the end of the line.

module.cor = nil
module.cancel = false
function module.Start()
	module.cor = coroutine.create(function()
		while not module.cancel do
			--other code
			game:GetService("RunService").Heartbeat:Wait()
		end
--yielding is not required since the body got executed and the coroutine is declared dead.
--if however there's more code after this and you want to kill it right here you will need to add: coroutine.yield() here and it will yield the curoutine. Making it not run anything, then destroy the reference. 
	end)
	module.cor.resume()
end

function module.Stop()
	module.cancel = true
	game:GetService("RunService").Heartbeat:Wait() --wait for the frame to end
	module.cor = nil
end
7 Likes

There is no way to kill a running coroutine, they are automatically dead once they code inside of them has been ran completely, once dead they also cannot be restarted, the main purpose of a coroutine is to create a new thread so that loops can run in parallel while being in the same script. while there is no formal way to do this you could write your own class using module scripts, and it wouldn’t be too hard, for example:

--Inside a module script
local CoroutineClass = {}
CoroutineClass.__index = CoroutineClass

function CoroutineClass:New(Function)
    local NewObj = {}
    setmetatable(NewObj,self)
    NewObj.Flag = true
    NewObj.Function = Function
    NewObj.Coroutine = NewObj:CreateCoroutine()
    return NewObj
end

function CoroutineClass:CreateCoroutine()
    return coroutine.create(function()
        while self.Flag do 
            self.Function()
            wait()
        end
    end)
end

function CoroutineClass:Start()
    --chechking if the coroutine is already running and if it is return
    if coroutine.status(self.Coroutine) == "running" then return end
    --checking if a coroutine has ran before already and if it has then we must create a new one
    if coroutine.status(self.Coroutine) == "dead" then 
        self.Coroutine = self:CreateCoroutine()
    end
    self.Flag = true
    coroutine.resume(self.Coroutine)
end

function CoroutineClass:Kill()
    self.Flag = false
end

return CoroutineClass

--inside of a local script / script
local CoroutineClass = --require the coroutine module

local function DoSomething()
	--[[
		only put here what
		you want to ran inside the loop 
		and not the loop itself
	--]]
	print("Go")
	wait()
	print("No")
end


local Coroutine = CoroutineClass:New(DoSomething)

Coroutine:Start()--use this line here to start/restart the coroutine
wait(5)
print("Coroutine Killed")
Coroutine:Kill()--use this line here to kill the coroutine
wait(5)
print("Coroutine Restarted")
Coroutine:Start()
8 Likes