常见协程切换操作介绍
更新时间: 2026/04/10
在Gitcode上查看源码

1 rpc调用

1.1 调用自动生成代码client.lua提供的rpc调用方法

自动生成代码示例

lua
function network_adapter_client:PSmsSmsGetChannelType(ctx, path_params, IsPowerOffPer)
    return pcall(function()
        local req = Sms.GetChannelTypeReq.new(IsPowerOffPer):validate()
        local obj = self:GetSmsSmsObject(path_params)
        return Sms.GetChannelTypeRsp.new(obj:GetChannelType(ctx, req:unpack(true)))
    end)
end

使用示例

lua
local client = require 'network_adapter.client'
client:PSmsSmsGetChannelType() -- 方法调用

1.2 使用自动生成代码client.lua获取的代理对象进行rpc调用

自动生成代码示例

lua
function network_adapter_client:GetBlockIOObjects()
    return get_non_virtual_interface_objects(self:get_bus(), 'bmc.kepler.Chip.BlockIO', true)
end

使用示例

lua
local client = require 'network_adapter.client'
local obj = client:GetBlockIOObjects()
obj:method() -- 方法调用

1.3 使用sd_bus库提供的call、timeout_call、pcall、timeout_pcall、sync_call等方法

框架公共库sd_bus接口

  • call(service_name, path, interface, method, signature, ...)
  • timeout_call(timeout_ms, service_name, path, interface, method, signature, ...)
  • pcall(service_name, path, interface, method, signature, ...)
  • timeout_pcall(timeout_ms, service_name, path, interface, method, signature, ...)
  • sync_call(service_name, path, interface, method, signature, ...)
lua
local ok, rsp = pcall(self.bus.call, self.bus, handler.ServiceName, handler.path, 'bmc.kepler.CmdInfo', 'Process', 'a{ss}ayay', ipmi_context, req.Payload, json.encode(ctx_table))

1.4 使用远程引用对象属性进行rpc调用

例:obj1对象包含远程引用对象属性,可以调用远程引用对象obj2资源树方法

lua
obj1.remote_ref_obj_prop = obj2

2 信号订阅

2.1 自动生成代码client.lua提供的接口新增、接口删除、属性变更、自定义信号订阅接口

lua
-- 接口新增
function network_adapter_client:OnBlockIOInterfacesAdded(cb)
    self.signal_slots[#self.signal_slots + 1] = subscribe_signal.on_interfaces_added(self:get_bus(), '/bmc', cb, 'bmc.kepler.Chip.BlockIO')
end
-- 接口删除
function network_adapter_client:OnBlockIOInterfacesRemoved(cb)
    self.signal_slots[#self.signal_slots + 1] = subscribe_signal.on_interfaces_removed(self:get_bus(), '/bmc', cb, 'bmc.kepler.Chip.BlockIO')
end
-- 属性变更
function network_adapter_client:OnFruCtrlPropertiesChanged(cb)
    self.signal_slots[#self.signal_slots + 1] = subscribe_signal.on_properties_changed(self:get_bus(), '/bmc', cb, 'bmc.kepler.Systems.FruCtrl', {'PowerState', 'SysResetDetected'})
end
-- 自定义信号订阅接口
function network_adapter_client:SubscribeLldpConfigLLDPOverNCSIStateChanged(cb)
    local sig = match_rule.signal('LLDPOverNCSIStateChanged', 'bmc.kepler.Managers.LldpConfig')
    self.signal_slots[#self.signal_slots + 1] = self:get_bus():match(sig, function(msg)
        cb(msg:read())
    end)
end

2.2 使用sd_bus库提供的match方法进行信号订阅

lua
self.lldp_receive_listener = bus:match(sig, function(msg)
    self:next_tick(function()
        local ok, lldp_rsp = pcall(libmgmt_protocol.vdpci_lldp_parser, msg:read())
        ...
        port:update_lldp_receive(lldp_rsp)
    end)
end)

3 属性设置

3.1 使用自动生成代码client.lua获取的代理对象设置属性

lua
local client = require 'network_adapter.client'
local obj = client:GetBlockIOObjects()
obj.prop_name = prop_value

3.2 使用远程引用对象设置被引用对象的属性

例:obj1.remote_ref_obj_prop引用远程对象obj2, obj1对象可设置obj2的属性

4 设置本组件属性

4.1 设置远程引用属性

lua
-- prop_name为远程引用属性
obj:set_prop(prop_name, prop_value)

lua
-- prop_name为远程引用属性
obj.prop_name = prop_value

4.2 设置其被引用属性在被设置时存在协程切换操作的本地引用属性

例:本地引用属性local_ref_prop_name,被引用属性prop在被设置时存在协程切换操作

lua
obj.local_ref_prop_name = prop

4.3 设置在property_before_change、property_changed回调处理中有切换协程操作的属性

lua
function network_adapter_client:OnFruCtrlPropertiesChanged(cb)
    self.signal_slots[#self.signal_slots + 1] = subscribe_signal.on_properties_changed(self:get_bus(), '/bmc', cb, 'bmc.kepler.Systems.FruCtrl', {'PowerState', 'SysResetDetected'})
end

4.4 使用本地引用对象设置被设置时会切换协程的被引用对象的属性

例:obj1.prop_a引用obj2,obj2.prop_b注册了属性变更信号会切换协程

lua
obj1.prop_a.prop_b = prop_value

5 读取属性

5.1 读取property_read回调处理中有切换协程操作的属性

lua
obj.property_read:on(function()
    -- 包含切换协程操作的属性
end)

5.2 读取引用属性

lua
-- prop_name为引用属性
obj:get_prop(prop_name)

lua
obj.ref_prop_name

5.3 使用自动生成代码client.lua获取的代理对象读取属性

lua
function fructl.get_system_reset_flag()
    local obj = fructl.get_fructl_obj()
    if not obj then
        log:error('[network_adapter]get_system_reset_flag: get power object failed')
        return nil
    end
    return obj.SysResetDetected
end

5.4 使用远程引用对象读取被引用对象的属性

例:obj1.remote_ref_obj_prop引用远程对象obj2,obj1对象可获取obj2的属性

5.5 使用本地引用对象读取,被读取时会切换协程的被引用对象的属性

例:本地引用对象obj,被引用对象的属性prop_name在被读取时会切换协程,obj获取属性prop_name,使用方法为obj.prop_name

6 其它libmc4lua库提供的API

6.1 任务管理机制task_mgmt

  • task_mgmt.update_task(id, data)
lua
task_mgmt.update_task(task_id, {State = task_state.Running, Progress = 20})

6.2 sd_bus提供的ping方法

lua
org_freedesktop_dbus.ping(bus, service_name, app_path)

7 其它自动生成代码中会切换协程的API

7.1 自动生成代码client.lua中获取代理对象的方法

以下API获取失败重试过程中会睡眠等待

  • get_non_virtual_interface_objects(bus, interface, retry)
  • foreach_non_virtual_interface_objects(bus, interface, cb, retry)

例如network_adapter组件自动生成代码

lua
function network_adapter_client:GetBlockIOObjects()
    return get_non_virtual_interface_objects(self:get_bus(), 'bmc.kepler.Chip.BlockIO', true)
end

function network_adapter_client:ForeachBlockIOObjects(cb)
    return foreach_non_virtual_interface_objects(self:get_bus(), 'bmc.kepler.Chip.BlockIO', cb, true)
end

8 skynet中会切换协程的API

同一个skynet服务中的一条消息处理中,如果调用了一个阻塞API,那么它会被挂起。挂起过程中,这个服务可以响应其它消息,产生协程切换行为,这很可能造成时序问题,开发过程中需要重点注意这些操作

API名称功能描述协程切换场景
skynet.sleep(ti)阻塞API,将当前coroutine挂起ti个单位时间,一个单位时间是0.01秒当前协程会被挂起指定的时间
skynet.yield相当于skynet.sleep(0)让出当前任务执行流程,使本服务内其它任务有机会执行
skynet.call阻塞住当前的coroutine,而没有阻塞整个服务。注意,在skynet.call之前获得的服务内的状态,到返回后,很有可能改变如果接收方没有立即回应,则当前协程会被挂起并等待,直到收到回应
skynet.wait挂起当前协程,之后由skynet.wakeup唤醒让出当前任务执行流程,直到使用wakeup唤醒它

9 案例介绍

如下伪代码,增加调用skynet.sleep(10)等待100毫秒后,当前协程被挂起,服务中其它协程在执行,产生了协程切换,执行时序发生了变化。新增前串行执行结果是collections集合中先保存后删除的path,新增后变成先删除后新增,最后collections集合依旧保留着path,结果与预期不一致

lua
local collections = {}
function classX:listen_sig_interfaces_added(sig_interfaces_added, source)
    return self.bus:match(sig_interfaces_added, function(msg)
        ...
        skynet(10)
        collections[path] = true
        ...
    end)
end
function classX:listen_sig_interfaces_removed(sig_interfaces_removed, source)
    return self.bus:match(sig_interfaces_removed, function(msg)
        ...
        collections[path] = nil
        ...
    end)
end

要避免协程切换导致的非预期结果,可以将串行执行的逻辑放到队列中执行,保证执行顺序符合预期,正常流程如下

lua
local collections = {}
function classX:listen_sig_interfaces_added(sig_interfaces_added, source)
    return self.bus:match(sig_interfaces_added, function(msg)
        local _, interfaces_and_properties = msg:read('oa{sa{sv}}')
        ...
        self.queue(function()
            skynet(10)
            collections[path] = true
        end)
        ...
    end)
end
function classX:listen_sig_interfaces_removed(sig_interfaces_removed, source)
    return self.bus:match(sig_interfaces_removed, function(msg)
        local _, interfaces_and_properties = msg:read('oa{sa{sv}}')
        ...
        self.queue(function()
            skynet(10)
            collections[path] = nil
        end)
        ...
    end)
end

skynet queue函数说明:queue函数调用得到一个新的临界区,可以保护一段代码不被同时运行。服务收到多条消息,一定是处理完一条后,才处理下一条,即使业务中包含有skynet.call这类的阻塞调用。一旦它们被挂起,新的消息到来后,新的处理流程会被排到队列尾部,等待前面的流程执行完毕才会开始