Lua-Web快速开发指南9-使用cf内置的异步库

48次阅读

共计 4358 个字符,预计需要花费 11 分钟才能阅读完成。

API 介绍

cf 框架提供内置的异步库cf, 需要使用的时候我们必须先导入 API: local cf = require "cf".

定时器与循环定时器

cf 库内置了一些定时器方法, 这些方法为开发者提供了对时间事件的控制能力. cf.timeoutcf.atcf.sleep.

cf.sleep方法是一个 阻塞 的定时器, 只有一个参数用来设置当前协程的休眠时间并且没有返回值. 此方法的行为 (语义) 取决于用户传入的参数:

  • 当时间参数大于 0 的时候, 当前协程会暂停指定的时间且让出执行权. 当指定的时间超时后函数将会返回继续执行下面的代码.
  • 当时间参数等于 0 的时候, 当前协程会暂停并且让出执行权. 当其它协程执行完毕 (让出) 后立刻返回.
  • 当时间参数小于 0 或者非 number 类型的时候, 此方法将立刻返回.

cf.timeoutcf.at 不会阻塞当前协程执行流程. 目前虽然暴露给开发者使用, 但真正的使用场景都仅限于在需要长连接业务内.

cf.timeoutcf.at 都会返回一个 timer 对象, 开发者可以在任何时候使用 timer 对象的 stop 方法停止定时器.

cf.timeoutcf.at 的参数如下:

  • 第一个参数是一个指定的时间, 其在现实中的时间比例为 1:1.
  • 第二个参数是一个回调函数, 当时间事件触发后将会为用户执行用户定义的回调函数.

记住: cf.timeout是一次性定时器, 回调函数被触发之后将会自动停止运行. 而 cf.at 如果不使用 stop 方法停止则会 一直重复执行.

协程的使用、暂停、唤醒

cf 库提供了 协程 的操作方法. 此 协程 与 Lua 的原生协程有些许不同, cf 基于原生协程的基础上由框架管理生命周期.

需要异步执行一个函数可以使用 cf.fork 创建一个由 cf 调度的协程, 此方法会返回一个协程对象. 这个协程对象可以在它让出的时候用来主动唤醒.

cf.fork方法的第一个参数 func 为 function 类型, 从第二个参数开始的参数将会作为 func 的参数(一般情况下我们会利用 upvalue 而不会显示传递参数).

需要暂停一个 cf 创建的协程可以使用 cf.wait 方法. 此方法没有参数, 但如果调用此方法的协程不是由 cf 创建或不是 main 协程则会出错.

cf.wakeup方法用于唤醒由 cf.wait 暂停的协程. cf.wait方法的返回值由 cf.wakeup 的行为决定, 当唤醒的是不存在的协程或唤醒正在执行的协程将会出错.

cf.wakeup方法的第一个参数是一个协程对象, 协程对象之后的所有参数将会返回给 cf.wait 进行接收.

需要获取当前协程对象的时候在这个协程执行流程之间使用 cf.self 方法获取, cf.self的作用与内置库 coroutine.running 方法相同.

它返回一个协程对象与一个 boolean 值. 当协程对象为主 (main) 协程时则 bolean 为true, 否则为false.

更多详细的 API 介绍

更多使用介绍请参考 cf 库的文档.

开始实践

1. 随机生成三个定时器并且输出时间.

在本示例中! 我们首先修改随机数生成器种子, 随机从 0~1 中间取一个随机数作为定时器的时间. 然后启动一个循环开始生成 3 个定时器.

-- main.lua
local cf = require "cf"
math.randomseed(os.time()) -- 设置随机数种子
for index = 1, 3 do
  local time = math.random()
  cf.timeout(time, function()
    print("第"..index.."个定时器的时间为:"..time)
  end)
end

由于是随机生成的时间, 所以我们在函数内部使用 print 方法将当前定时器的运行信息打印出来(第几个创建的定时器与定时器时间).

现在让我们多运行几次来查看输出有什么不同:

[candy@MacBookPro:~/Documents/core_framework] $ ./cfadmin
第 2 个定时器的时间为:0.0029842634685338
第 1 个定时器的时间为:0.12212080322206
第 3 个定时器的时间为:0.38623028574511
[candy@MacBookPro:~/Documents/core_framework] $ ./cfadmin
第 1 个定时器的时间为:0.10055952938274
第 3 个定时器的时间为:0.30804532766342
第 2 个定时器的时间为:0.32007071143016
[candy@MacBookPro:~/Documents/core_framework] $ ./cfadmin
第 3 个定时器的时间为:0.083867806941271
第 2 个定时器的时间为:0.52678858255967
第 1 个定时器的时间为:0.74910803744569

可以看到, 每次的输出内容因为随机数产生的数值不同而不同.

2. 定时器的启动与暂停

一个定时器的启动与停止必然是相对应的. 下面这个示例展示了如何启动与

-- main.lua
local cf = require "cf"
local timer = cf.timeout(1, function ()
  print("定时器触发")
end)

timer:stop()

在上述这段代码中, 我们启动了一个 1 秒的一次性定时器并且获取了一个 timer 的对象.

这个定时器会在 超时 主动停止 后停止运行, 即使多次对同一个定时器对象调用 stop 方法也是无害的操作.

如果不出意外的情况下, 开发者应该会看到这样的输出内容:

[candy@MacBookPro:~/Documents/core_framework] $ ./cfadmin
定时器触发
[candy@MacBookPro:~/Documents/core_framework] $

如果您注释掉 timer:stop() 这段代码, 将不会有任何内容输出.

3. 启动一个协程来实现异步任务.

cf 的协程是任务执行的关键模块, 我们利用协程可以达到异步任务的使用效果.

下面这个示例展示了如何使用协程来执行异步任务:

-- main.lua
local cf = require "cf"

print("主协程开始运行..")

cf.fork(function ()
  print("cf 的协程开始运行..")
end)

print("主协程开始休眠..")
cf.sleep(1)
print("主协程结束休眠..")

首先我们在主协程中创建了一个 cf 协程, 这个线程将在主协程睡眠 (让出执行权) 期间运行.

当主协程休眠结束后将继续运行. 所以, 它的输出应该是这样子的:

[candy@MacBookPro:~/Documents/core_framework] $ ./cfadmin
主协程开始运行..
主协程开始休眠..
cf 的协程开始运行..
cf 的协程结束运行..
主协程结束休眠..

4. 协程之间的互相作用

我们来假设一个场景: 主协程创建一个协程执行循环计算任务, 当任务执行完毕后唤醒主协程并且将计算结果传递过来.

思路: 首先我们利用前面学到的 API 获取主协程对象, 然后创建一个新的协程来执行计算. 计算完成之后将结果返回.

-- main.lua
local cf = require "cf"

local co = cf.self()
print("主协程开始运行..")

cf.fork(function ()
  print("cf 协程开始运行..")
  local result = 0
  for index = 1, 100 do
    result = result + index
  end
  print("cf 协程运行完毕, 返回结果并且唤醒主协程..")
  return cf.wakeup(co, result)
end)

print("主协程休眠等待计算完成..")
local result = cf.wait()
print("主协程休眠结束获取到结果为:"..result)
print("主协程执行完毕..")

输出结果如下所示:

[candy@MacBookPro:~/Documents/core_framework] $ ./cfadmin
主协程开始运行..
主协程休眠等待计算完成..
cf 协程开始运行..
cf 协程运行完毕, 返回结果并且唤醒主协程..
主协程休眠结束获取到结果为:5050
主协程执行完毕..

注意: 上述示例仅用于演示如何创建异步任务, 对 CPU 密集型运算毫无帮助. 真实使用场景一般是在 IO 密集型运算中使用.

5. 两种不同的循环定时器

首先我们根据上述的 API 创建 标准 API提供的循环定时器:

local cf  = require "cf"

local timer
local index = 1

timer = cf.at(1, function ()
  if index > 10 then
    print("定时器停止运行..")
    return timer:stop()
  end
  print("输出的数值为:", index)
  index = index + 1
end)

-- timer:stop()

输出如下:

[candy@MacBookPro:~/Documents/core_framework] $ ./cfadmin
输出的数值为:    1
输出的数值为:    2
输出的数值为:    3
输出的数值为:    4
输出的数值为:    5
输出的数值为:    6
输出的数值为:    7
输出的数值为:    8
输出的数值为:    9
输出的数值为:    10
定时器停止运行..
[candy@MacBookPro:~/Documents/core_framework] $

上述代码在每次循环定时器超时的时候都会执行回调函数输出当前自增数值, 最后在达到一定次数后自动停止运行. 这种形式的写法也是作者所推荐的写法.

当然, 我们还有一种利用 cf.sleep特殊 的定时器写法:

cf.fork(function ()
  for index = 1, 10 do
    cf.sleep(1)
    print("输出的数值为:", index)
  end
  print("定时器停止运行..")
end)

它的输出如下:

[candy@MacBookPro:~/Documents/core_framework] $ ./cfadmin
输出的数值为:    1
输出的数值为:    2
输出的数值为:    3
输出的数值为:    4
输出的数值为:    5
输出的数值为:    6
输出的数值为:    7
输出的数值为:    8
输出的数值为:    9
输出的数值为:    10
定时器停止运行..
[candy@MacBookPro:~/Documents/core_framework] $

两种写法虽然行为上是一致, 但是两种不同的定时器的内部实现行为却是不一样.

第一种定时器无论在可读性与可控性上来看都做的非常好. 第二种虽然能模拟第一种的行为, 但是在内部需要多创建一个协程来实现唤醒并且无法被外部停止.

而且必须使用第二种定时器的场景一般是不存在的, 但是为了演示还是需要知会开发者尽量不要编写毫无好处的代码 :).

继续学习

下一章我们将学习如何使用 MQ 库来完成消息队列的任务监听.

正文完
 0