怎樣寫一個相似ROS的易用的android機器人框架(3)android
爲避免機器人執行多任務時對傳感器,執行機構的佔用衝突,同時又有知足機器人響應突發任務的需求,設計這樣的任務框架:多線程
多個任務是排隊執行的,即同一時間只有一個任務處於運行狀態,任務執行過程當中若是有新任務到來並容許容許,當前任務會暫停,保存任務進度和狀態後,再執行新任務,新任務結束後,再恢復執行暫停保存的任務。 每一個任務都有onStart
,onStop
,onPause
,onResume
四個生命週期,以便根據任務的不一樣狀態進行相應的任務參數設置。 當沒有任務時,系統插入一個用戶定義的空閒任務,用於通知系統進入待機模式或者控制顯示待機界面。 完整的流程以下框架
IdelTask onStart
-> IdleTask onStop
-> Task onStart
-> Task onPause
-> NewTask onStart
-> NewTask onStop
-> Task onResume
-> Task onStop
-> IdleTask onStart
async
當前任務不可恢復時,不會走 onPause
,onResume
,流程以下: IdelTask onStart
-> IdleTask onStop
-> Task onStart
-> Task onStop
-> NewTask onStart
-> NewTask onStop
-> IdleTask onStart
函數
當前任務可恢復但不可打斷,可是用戶又想要當即執行新任務時,經過接口通知用戶選擇馬上結束或暫停當前任務仍是保持現狀。保持現狀則新任務只能等到當前任務自行結束後自行,不然,將暫停或者結束當前任務,而後走如上的流程oop
代碼位於 ai.easy.robot.task
包名下this
先定義任務接口,以便實現不一樣的擴展,接口定義以下:.net
// 機器人任務接口,因爲處理其行爲邏輯 interface ITask { // 任務名稱 val name: String // 任務正在運行 val isAlive: Boolean // 任務是否能夠恢復 fun canResume(): Boolean //任務開始時觸發 fun onStart(ctx: TaskContext) //任務結束時觸發 fun onStop(ctx: TaskContext) //任務暫停時觸發 fun onPause(ctx: TaskContext) //任務恢復時觸發 fun onResume(ctx: TaskContext) }
接着定義TaskPool, TaskPool是實現任務調度的類,其用過一個等待任務隊列,保存等待執行的任務,還有一個任務棧,保存已經執行的和被暫停保存起來的任務線程
private val waitingTask: LinkedList<TaskTableItem> = LinkedList() private val taskStack: Stack<TaskTableItem> = Stack()
taskStack的棧頂元素即爲當前任務設計
TaskTableItem
爲任務的信息類 定義以下:
internal data class TaskTableItem( val name: String, val task: ITask, val ctx: TaskContext, val startData: Bundle? = null, val addedTime: Long, var startTime: Long = -1, //開始時間,用於延時任務 var pauseTime: Long = -1, var canInterrupted: Boolean = true, var forceStop: Boolean = false , var priority: Int = 0 )
每一個任務都有獨立的與之綁定的 TaskContext , 隨着任務 onStart 時建立,onStop 時釋放。
經過調用 TaskPool 的 mainLoop()
執行任務列表的調度流程,具體流程見代碼,爲了不多線程開銷,這裏用了kotlin的協程實現:
val idleCtx = TaskContext(this) idleCtx.cc = context while (looping) { //是否有新任務 if (waitingTask.size > 0) { val ti = waitingTask.peek() //檢測任務添加時間是否失效 if (checkOutDate(ti!!)) { waitingTask.poll() continue } val wanna_task = ti.task // // if (isIdle) { myIdleTask.onStop(idleCtx) log("Idle任務中止") isIdle = false val ctx = ti.ctx ctx.startData = ti.startData ?: idleCtx.startData ctx.cc = context // idleCtx.release() log("${wanna_task.name}任務開始") waitingTask.poll() wanna_task.onStart(ctx) } else if (taskStack.size > 0) { //當前有任務在運行 val cur = taskStack.peek() val cur_task = cur.task val cur_ctx = cur.ctx //當前任務不可打斷 if (!cur.canInterrupted && cur_task.isAlive) { delay(20) continue } // if (cur_task.canResume() && cur_task.isAlive && !cur.forceStop) { cur_task.onPause(cur_ctx) log("${cur_task.name}任務暫停") } else { // cur_task.onStop(cur_ctx) log("${cur_task.name}任務中止") cur_ctx.release() taskStack.pop() // } // ti.ctx.startData = ti.startData ?: cur_ctx.startData ti.ctx.cc = context log("${wanna_task.name}任務開始") waitingTask.poll() wanna_task.onStart(ti.ctx) } //將可恢復任務壓入堆棧 taskStack.push(ti) // continue } //沒有新任務時 //檢查當前是否還有任務,彈出失效的任務 if (taskStack.size > 0) { val cur = taskStack.peek() val cur_task = cur.task val cur_ctx = cur.ctx if (!cur_task.isAlive) { cur_task.onStop(cur_ctx) log("${cur_task.name}任務中止") //任務已中止,彈出 taskStack.pop() cur_ctx.release() // if (taskStack.size > 0) { taskStack.peek().let { log("${it.task.name}任務恢復") it.task.onResume(it.ctx) } } // continue } } //無任何任務,轉入空閒狀態 if (taskStack.size == 0) { if (!isIdle) { log("Idle任務開始") idleTask?.onStart(idleCtx) isIdle = true } } // delay(25) //掛起防止線程堵塞 }
經過調用 TaskPool 的 addNewTask()
通知TaskPool執行新的任務。
須要注意
須要根據新任務的優先級插入到合適的位置。
經過 InterruptComingSelector
的實現類顯示UI通知用戶進一步的操做,InterruptComingSelector
的定義以下:
/** * 詢問用戶是否打斷任務的通知器 */ abstract class InterruptComingSelector { companion object { const val CHOICE_STOP = 1 const val CHOICE_PAUSE = 2 const val CHOICE_DO_NOTHING = 0 } /** * 開始詢問用戶時 * @param taskName 當前任務名 * @param canResume 當前任務可恢復 */ abstract fun makeSelection(taskName: String, canResume: Boolean) /** * 當前任務已結束或者用戶取消選擇時 */ abstract fun onFinishOrCancel() internal var isFinished: Boolean = false internal var userChoice: Int = -1 /** * 設置用戶的選擇結果 * @param choice * @see CHOICE_STOP * @see CHOICE_PAUSE * @see CHOICE_DO_NOTHING */ fun finish(choice: Int) { userChoice = choice isFinished = true } }
InterruptComingSelector
的生命週期是由 TaskContext 管理的,以便任務結束而用戶還未作出選擇時 銷燬其顯示的UI
TaskContext 實現提供任務內資源的管理,提供與TaskPool的通信,提供任務定時器等功能。
同時TaskContext經過一個子任務隊列功能。子任務是比任務更小的任務,沒有暫停和恢復選項,子任務只能按順序一個個執行,可是當個子任務的run()
執行函數內能夠並行運行多個函數,這些實際上是用協程實現的。子任務定義以下:
interface ISubTask { /** * 設置執行任務超時,一旦超時,將結束子任務run()並觸發onCancel(), 單位: ms */ fun timeout(): Int /** * 子任務的主要工做 * @return 決定下個子任務是否繼續 */ fun run(paraMgr: SubTaskParallelManager): Boolean /** * 子任務正常結束或者運行超時時觸發 * @param isTimeout * @return 決定下個子任務是否繼續 */ fun onCancel(isTimeout: Boolean): Boolean }
TaskContext.createSubTaskQueue()
建立一個子任務隊列,該隊列會在TaskContext釋放時釋放。
add()
往隊列了添加子任務start()
開始運行隊列中的子任務forceCancel()
是取消剩下的子任務,清空隊列pauseSubTasks()
是在任務 onPause時暫停隊列運行resumeSubTasks()
是在任務 onResume時恢復隊列運行start()的實現以下:
fun start(whenFinish: () -> Unit) { work = async(ctx.cctx) { // var ok = true while (isActive && queue.size > 0 && ok) { //暫停時掛起 if (isPause) { delay(100) continue } val sub = queue.poll() val t = sub.timeout().toLong() val timeout = if (t < 100L) 100L else t var is_timeout = false try { withTimeout(t) { currJob = async(context) { sub.run(SubTaskParallelManager(ctx.cctx)) } ok = currJob?.await() ?: false } } catch (e: Throwable) { e.printStackTrace() is_timeout = true } finally { val r = sub.onCancel(is_timeout) ok = if (is_timeout) r else ok } } // currJob = null isFinished = true whenFinish() true } }
這個任務框架是經過檢查 ITask的 isAlive 的值判斷任務結束的,用戶擴展ITask時須要在任務結束時將isAlive設爲false
任務的onStart
,onStop
,onPause
,onResume
不該該堵塞線程,若是是長時間運行的任務,可經過TaskContext.doMainWork()
來執行, 配合TaskContext.delayMs()
來延時。若是doMainWork()
中有循環, 須要在循環體中調用 suspendMainWorkWhenPaused()
, 這樣任務暫停是調用pauseMainWork()
, 就能在suspendMainWorkWhenPaused()
處掛起。