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

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个定时器.

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

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

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

  1. [candy@MacBookPro:~/Documents/core_framework] $ ./cfadmin
  2. 2个定时器的时间为:0.0029842634685338
  3. 1个定时器的时间为:0.12212080322206
  4. 3个定时器的时间为:0.38623028574511
  5. [candy@MacBookPro:~/Documents/core_framework] $ ./cfadmin
  6. 1个定时器的时间为:0.10055952938274
  7. 3个定时器的时间为:0.30804532766342
  8. 2个定时器的时间为:0.32007071143016
  9. [candy@MacBookPro:~/Documents/core_framework] $ ./cfadmin
  10. 3个定时器的时间为:0.083867806941271
  11. 2个定时器的时间为:0.52678858255967
  12. 1个定时器的时间为:0.74910803744569

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

2. 定时器的启动与暂停

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

  1. -- main.lua
  2. local cf = require "cf"
  3. local timer = cf.timeout(1, function ()
  4. print("定时器触发")
  5. end)
  6. timer:stop()

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

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

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

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

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

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

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

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

  1. -- main.lua
  2. local cf = require "cf"
  3. print("主协程开始运行..")
  4. cf.fork(function ()
  5. print("cf的协程开始运行..")
  6. end)
  7. print("主协程开始休眠..")
  8. cf.sleep(1)
  9. print("主协程结束休眠..")

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

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

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

4. 协程之间的互相作用

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

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

  1. -- main.lua
  2. local cf = require "cf"
  3. local co = cf.self()
  4. print("主协程开始运行..")
  5. cf.fork(function ()
  6. print("cf协程开始运行..")
  7. local result = 0
  8. for index = 1, 100 do
  9. result = result + index
  10. end
  11. print("cf协程运行完毕, 返回结果并且唤醒主协程..")
  12. return cf.wakeup(co, result)
  13. end)
  14. print("主协程休眠等待计算完成..")
  15. local result = cf.wait()
  16. print("主协程休眠结束获取到结果为:"..result)
  17. print("主协程执行完毕..")

输出结果如下所示:

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

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

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

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

  1. local cf = require "cf"
  2. local timer
  3. local index = 1
  4. timer = cf.at(1, function ()
  5. if index > 10 then
  6. print("定时器停止运行..")
  7. return timer:stop()
  8. end
  9. print("输出的数值为:", index)
  10. index = index + 1
  11. end)
  12. -- timer:stop()

输出如下:

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

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

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

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

它的输出如下:

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

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

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

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

继续学习

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