0Pricing
Lua Academy · 课时

协程状态

使用 coroutine.status 检查协程状态:running、suspended、dead。

协程状态 是 CoddyKit 上的免费 Lua Academy 课时。 这是第 3 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Lua Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Lua Academy 课程共包含 4 节课。

协程的四种状态

协程有四种可能的状态:挂起(刚创建或执行了 yield,正在等待运行)、运行(当前正在执行)、正常(暂停运行,以便另一个协程运行)和已结束(执行完成或发生错误)。使用 coroutine.status(co) 查询状态。

local co = coroutine.create(function()
  print(coroutine.status(coroutine.running()))  -- running
  coroutine.yield()
end)

print(coroutine.status(co))  -- suspended
coroutine.resume(co)          -- prints: running
print(coroutine.status(co))  -- suspended
coroutine.resume(co)
print(coroutine.status(co))  -- dead

“正常”状态

当一个协程恢复另一个协程时,它会进入“正常”状态。它并未运行(因为另一个协程正在运行),但也不是挂起状态——它正在等待被恢复的协程 yield。对于复杂的协程关系图,虽然这种状态并不常见,但理解它很重要。

local inner, outer

inner = coroutine.create(function()
  print("outer status:", coroutine.status(outer))  -- normal
  coroutine.yield()
end)

outer = coroutine.create(function()
  coroutine.resume(inner)
end)

coroutine.resume(outer)
-- prints: outer status: normal

恢复前进行检查

恢复协程前,请务必检查其状态,尤其是在循环中。恢复已结束的协程会产生错误。恢复正在运行的协程同样会产生错误(因为这会导致无限递归)。

local function safeResume(co, ...)
  local status = coroutine.status(co)
  if status == "dead" then
    return false, "cannot resume dead coroutine"
  end
  if status == "running" then
    return false, "cannot resume running coroutine"
  end
  return coroutine.resume(co, ...)
end

发生错误后的状态

当协程主体函数引发未捕获的错误时,coroutine.resume 会返回 false 和错误消息。协程会转为“已结束”状态。它无法重新启动——如果要重试,必须创建一个新的协程。

local co = coroutine.create(function()
  error("something went wrong")
end)

local ok, err = coroutine.resume(co)
print(ok, err)                      -- false  ...: something went wrong
print(coroutine.status(co))         -- dead

-- Cannot resume dead coroutine:
local ok2, err2 = coroutine.resume(co)
print(ok2, err2)  -- false  cannot resume dead coroutine

coroutine.isyieldable

如果正在运行的协程可以执行 yield,coroutine.isyieldable() 就会返回 true。在主线程以及不支持 yield 的 C 函数中,它会返回 false。如果您不确定当前上下文,请在执行 yield 前进行检查。

local co = coroutine.create(function()
  print("isyieldable:", coroutine.isyieldable())  -- true
  coroutine.yield()
end)

coroutine.resume(co)

-- In main thread:
print("isyieldable:", coroutine.isyieldable())  -- false
-- coroutine.yield()  -- ERROR in main thread

监控一组协程

跟踪一个协程池,并在每轮恢复调用后移除已结束的协程。这是任务调度器的基础。

local function makeWorker(n, name)
  return coroutine.create(function()
    for i = 1, n do
      print(name, "step", i)
      coroutine.yield()
    end
  end)
end

local workers = {
  makeWorker(2, "fast"),
  makeWorker(4, "slow"),
}

while #workers > 0 do
  local alive = {}
  for _, co in ipairs(workers) do
    coroutine.resume(co)
    if coroutine.status(co) ~= "dead" then
      alive[#alive+1] = co
    end
  end
  workers = alive
end

协程调试

使用状态检查来记录和调试基于协程的代码。打印状态转换,以便了解执行流程。在复杂系统中,可以通过包装器表为协程分配名称,从而获得更易读的调试输出。

local function namedCo(name, fn)
  local co = coroutine.create(fn)
  return {
    co = co,
    name = name,
    resume = function(self, ...)
      print("[" .. self.name .. "] resume")
      local results = table.pack(coroutine.resume(self.co, ...))
      print("[" .. self.name .. "] status: " .. coroutine.status(self.co))
      return table.unpack(results, 1, results.n)
    end
  }
end

local worker = namedCo("worker1", function()
  coroutine.yield()
end)
worker:resume()
worker:resume()

重新启动已结束的协程

协程一旦结束,就无法再恢复。要“重新启动”它,必须使用同一个函数创建新的协程。常见的做法是使用工厂函数,按需创建全新的协程。

local function makeCounter(start, step)
  return coroutine.wrap(function()
    local n = start or 0
    while true do
      coroutine.yield(n)
      n = n + (step or 1)
    end
  end)
end

local count = makeCounter(0, 2)
for i = 1, 5 do io.write(count() .. " ") end
print()  -- 0 2 4 6 8

-- "Restart" by creating a new one
count = makeCounter(100)
print(count())   -- 100

Coroutine.wrap 的错误行为

包装后的协程发生错误时,错误会从包装器函数调用中传播出来。与会返回 false+错误信息的 coroutine.resume 不同,由 wrap 返回的函数会直接引发错误。如果需要捕获错误,请在 wrap 调用外使用 pcall。

local gen = coroutine.wrap(function()
  coroutine.yield(1)
  error("oops")
  coroutine.yield(2)  -- never reached
end)

print(gen())   -- 1 (ok)
local ok, err = pcall(gen)
print(ok, err) -- false  ...: oops
-- Subsequent call: dead coroutine
ok, err = pcall(gen)
print(ok, err) -- false  cannot resume dead coroutine

isyieldable 与 status 的区别

有两种方式可以查看协程上下文:coroutine.status(co) 提供特定协程的状态;coroutine.isyieldable() 用于判断当前执行上下文是否可以 yield。使用 status 检查其他协程;使用 isyieldable 检查当前环境是否可以安全地执行 yield。

local function maybeYield()
  if coroutine.isyieldable() then
    coroutine.yield()
  else
    -- in main thread or non-yieldable context
    print("cannot yield here")
  end
end

maybeYield()  -- prints "cannot yield here" (main thread)

local co = coroutine.create(maybeYield)
coroutine.resume(co)   -- yields successfully

快速检查

协程执行完主体函数后会进入什么状态?

回顾:协程状态

总结:

  • 状态:挂起 → 运行 ↔ 正常 → 已结束
  • coroutine.status(co) 返回状态字符串
  • 已结束 = 执行完成或发生错误;无法再次恢复
  • coroutine.isyieldable() 检查当前上下文是否可以 yield
  • 在循环中恢复协程前,请务必检查状态
  • wrap 的错误会直接传播;resume 返回 false+错误

常见问题解答

「协程状态」课时是免费的吗?

是的 — 「协程状态」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Lua Academy 课程的其余内容,请升级到 CoddyKit PRO。 Lua Academy 课程共包含 4 节课。

「协程状态」这节课中我会学到什么?

使用 coroutine.status 检查协程状态:running、suspended、dead。 你通过在浏览器中直接运行的动手代码来练习 Lua Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 Lua Academy 需要有经验吗?

无需任何先前经验。CoddyKit 上的 Lua Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 3 节课,共 4 节。

「协程状态」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 Lua Academy 课中编写并运行代码吗?

能。每节 Lua Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. 创建协程
  2. resume 和 yield
  3. 协程状态
  4. 生产者-消费者模式
← 返回 Lua Academy