后台任务:概述

  • 有条件地允许一个前台和一个后台任务同时运行,条件是不存在权限冲突。但不会同时运行两个后台任务。
  • 后台任务分为三类优先级:首先是在定时任务前执行的系统任务,然后是定时任务,最后是定时任务后执行的系统任务。
  • 注意区分timing任务和定时任务,timing虽然意思是定时,但timing任务不仅仅用于定时任务,还用于type_aplt_task类型的临时任务。它定义在小程序settings.cfg的timing块,所以称为timing任务。
  • 执行后台任务时,一旦要运行小程序,内中libroseaplt.so是以driver方式加载,加载类型apltsotype_bgaplt,字面“bg”是background的缩写。另外,直接运行小程序,即前台任务时,加载类型是apltsotype_curraplt。
  • 任务都会绑定一个小程序,这包括任何一种类型的后台任务。instance->current_aplt_指向前台任务绑定的小程序,timing_.aplt_则指向后台任务绑定的小程序。
  • 后台任务绑定的小程序不可能是前台任务小程序。换句话说,当有前台任务在运行时,禁止运行以它为小程序的后台任务。
  • 后台任务限制:不能弹出界面。有重要信息放到日志,或声音。

 

除直接运行小程序外,其它的都可归为后台任务。直接运行小程序是指在桌面点击小程图标,然后小程序被启动,代码入口是lua中的[aplt].start()。

图1 后台任务

后台任务分为两类,定时任务和系统任务。

  1. 定时任务。由用户预先在定时模块添加,写在一个定时任务列表中。
  2. 系统任务。非定时列表触发,触发方式像语音,中心发命令,检测到快没电了,等等。首先分为临时任务和自动回充。临时任务又分为“去[位置][timing任务](type_aplt_task)”,“去[位置](type_moveto)”,“识别图像(type_recognition)”,“去充电(type_charge)”。临时任务中去充电是收到命令后,不管电量多少,立即去充电;自动回充是机器人检测到电量低了,自动回去充电。

定时任务,以及系统任务中的type_aplt_task,它们执行的任务是同一种,是某个小程序向外提供的任务。它定义在settings.cfg的timing块中,也称为timing任务。注意,timing虽然意思是定时,但timing任务不仅仅用于定时任务。既然是在小程序,这就涉及到如何加载该小程序libroseaplt.so。它是以着driver加入,类型是apltsotype_bgaplt。

存在5种类型driver,其中apltsotype_curraplt, apltsotype_bgaplt不是表示驱动,但还是drivers管理着。

  • apltsotype_curraplt。直接运行的小程序,即前台任务。
  • apltsotype_bgaplt。后台任务中的小程序。后台任务中任何类型都会绑定一个小程序,像自动回充,它会绑定提供底盘驱动的小程序。

不论定时任务,还是type_aplt_task,执行的都是timing任务,在代码中以着同一个函数去启动(start_aplt_task)和结束(stop_aplt_task)。

后台任务限制:不能弹出界面。只是简单弹个提示框,都是不允许的。有重要信息放在日志,或声音。

 

一、抢占、优先级

任一时刻,要有条件地允许一个前台和一个后台任务同时运行。举个例子,前台任务是一个聊天小程序,只是用到大模型,不涉及导航、蓝牙权限。那要允许执行去窗台打开窗帘的定时任务。上面也说,这是有条件的,条件是不允许存在冲突权限。

前台任务有着最高优先级。直接启动小程序时,要是有正运行着的后台任务,且存在权限冲突,会先结束后台任务。

同是后台任务也存在抢占。具体表现在执行一个临时任务时,要是有后台任务正在运行,会先结束掉那个后台任务。这时不管有没有存在权限冲突,因为任何时刻只能有一个后台任务。

启动前台任务是执行applet::texecutor::run(),这逻辑较简单,相比来说,启动后台任务要复杂些,需要ttiming::shedule进行调度。

 

1.1 ttiming::shedule 

ttiming::shedule()是调度后台任务的时间片函数,它在app时间片内被调用。它决定了三种后台任务的优化级。

<librose>/aplt.cpp
------
void ttiming::shedule()
{
	VALIDATE(aplt_ == nullptr, null_str);

aplt_指向绑定到正运行后台任务的小程序,如果非nullptr,意味着有正运行的后台任务,禁止进入此函数。

	......
	{
		applet::ttiming::tsys_task* sys_task = instance->timing_sys_shedule(true);
		if (sys_task != nullptr) {
			start_sys(*sys_task);
			return;

找到了一个需在定时任务前执行的系统任务,目前就临时任务,执行它。

		}
	}
	......
	// 一旦找到了现在要执行的定时任务,hit_task则指向这个定时任务。
	if (hit_task == nullptr) {
		applet::ttiming::tsys_task* sys_task = instance->timing_sys_shedule(false);
		if (sys_task != nullptr) {
			start_sys(*sys_task);

找到了一个优先级比定时任务低,需执行的系统任务,目前就自动回充,执行它。

		}
		return;
	}
	......
	start_aplt_task(*hit_aplt, *new_task, nullptr);

这些代码是执行hit_task这个定时任务。

}

后台任务分为三类优先级:首先是在定时任务前执行的系统任务,然后是定时任务,最后是定时任务后执行的系统任务。

具体到任务上,语音、中心发命令触发的临时任务,是即时产生,需要立即响应,因而放时定时任务之前。临时任务中的“临时”就有要立即执行意思。对自动回充,毕竟不是不充电就立即不能用了,于是放在定时任务之后。

 

二、base_instance::timing_sys_shedule

timing_sys_shedule用于调度某个系统任务去执行,参数pre指示此时是在定时任务前,还是在定时任务后。

/base_instance.cpp
------
applet::ttiming::tsys_task* base_instance::timing_sys_shedule(bool pre)
{
	bool disallowed = false;
	if (applet::tdisable_new_aplt_lock::disabled()) {
		disallowed = true;

tdisable_new_aplt_lock::disabled()是true,表示当前禁止执行后台任务。

	} else {
		const applet::tapplet* running_aplt = instance->current_aplt();
		if (running_aplt != nullptr) {
			if (running_aplt->permissions.count(applet::PERMISSION_ID_NAVIGATION)) {
				disallowed = true;
			}

(需完善1)running_aplt指向前台任务小程序,认为后台任务总是需要导航权限,于是为简单,只要前台任务有导航权限,就认为不能运行后台任务。

		}
	}

	if (disallowed) {
		app_sys_timing_disallowed(pre);

当前不允许执行后台任务。这个后台任务有可能是语音触发的临时任务,说话人正等着,为让知道为什么没执行,在app_sys_timing_disallowed给个语音提示啥的。

		return nullptr;
	}

	return app_timing_sys_shedule(pre);

app_timing_sys_shedule返回需运行的系统任务。检查和当前任务的权限是否有冲突是放在系统任务的shedule,这里如果返回不是nullptr,权限肯定没冲突。

}

tdisable_new_aplt_lock::disabled()是true,表示当前禁止执行后台任务。同时它也会禁止执行定时任务,代码在schedule()。以下是几种会禁止情况。

enum描述
reason_map正在显示“地图”窗口。可能会修改当前地图信息
reason_settings正在显示“设置”窗口。可能会修改驱动要使用哪个小程序
reason_dnn正在显示“DNN”窗口
reason_moveit正在显示“机械臂”窗口
reason_netxmit正在用ihttp_cswamp,包括下载小程序,升级launcher、拼音包等

reason_moveit计划留留给机械臂,当前未使用。

 

三、tros_instance::ttask

ttask让当前环境具有ros操作能力。具体说,会确保存在能获得tf树的tf2_ros::Buffer,能让回调到接收导航结果的did_move_base_result,等等。和ros相关的两个主要操作是导航和机械臂,在它们执行期间,须确保系统中至少存在一个ttask。

导航、机械臂,往往是后台任务中做的一部分操作,尤其导航,好多后台任务的第一阶段就是它。导航会创建ttask,不免就会把ttask和后台任务联系在一起。但无论无何,ttask功能是让当前环境具有ros操作能力。如果一个前台任务须要导航、机械臂,照样会创建ttask,后面“lua代码中导航”会说到这个。

开始导航阶段的入口函数是base_instance::lua_did_navigation_start,在执行这函数期间,会创建ttask。如果之前有一个,会先销毁,再重新创建。

因为存在lua_did_navigation_start时创建的ttask,即使地图界面这些都销毁了,还有它在确保证能执行ros操作。那什么时候销毁ttask。

  1. (后台任务或前台小程序)导航结束,app会收到did_move_base_result。如果luafunc指定的任务不是机械臂,是timing任务、lua函数这些,会erase_task。为什么机械臂时不销毁task?——操作机械臂须要ros环境,免得重新创建task。
  2. (后台任务)如果luafunc指定任务是机械臂,那要等到操作完机械臂,结束此个后台任务,执行app_post_stop_bg_task()时。另外,如果提前中止后台任务中导航,也是在这里erase_task。
  3. (前台小程序)想提前中止导航,方法是关闭当前窗口,关闭窗口(APLT_MSG_DIDDLGCLOSE)时会erase_task。退出小程序一定会关闭窗口,所以退出也能中止导航。

 

四、lua代码中导航

前台小程序会须到导航,形式是“导航+lua代码”。让想像下空调小程序,要求把空调调到25度。那它执行步骤是先移到空调位置,再调25度温。为可灵活设置温度值,调温不是作为一个timing任务,而是段lua代码。

1、(lua,小程序)navigation_goal_:set_did_left_click("click_navigation_goal");

set_did_left_click用于挂接当用户单击navigation_goal_按钮后,会执行什么操作。一旦单击按钮,将执行个lua函数:click_navigation_goal。

2、(lua,小程序)widget:navigation_start(uuid, "did_navigation_goal");

在click_navigation_goal中执行这条语句,也就是说,当用户单击navigation_goal_按钮后,会执行它。

widget是一个从tcontrol派生的控件类型,像此刻正单击的按钮。参数uuid是要移动到位置的uuid。did_navigation_goal是导航结束后要执行的lua函数。

navigation_start是个C实现函数,它会调用lua_did_navigation_start启动导航,后者会调用ros_instance_.moveto_guid。

 

导航结束,会调用did_move_base_result。在那里会调用lua代码写的did_navigation_goal。并会erase_task。如果想提前中止导航,那关闭当前窗口,或退出小程序。这整个过程有创建、销毁ttask,但没有后台任务。

全部评论: 0

    写评论: