怎樣寫一個相似ROS的易用的android機器人框架(4)

怎樣寫一個相似ROS的易用的android機器人框架(4)

怎樣寫一個相似ROS的易用的android機器人框架(3)android

機器人任務框架的工做流程

爲避免機器人執行多任務時對傳感器,執行機構的佔用衝突,同時又有知足機器人響應突發任務的需求,設計這樣的任務框架:多線程

1.當前任務可打斷可恢復時

多個任務是排隊執行的,即同一時間只有一個任務處於運行狀態,任務執行過程當中若是有新任務到來並容許容許,當前任務會暫停,保存任務進度和狀態後,再執行新任務,新任務結束後,再恢復執行暫停保存的任務。 每一個任務都有onStart,onStop,onPause,onResume四個生命週期,以便根據任務的不一樣狀態進行相應的任務參數設置。 當沒有任務時,系統插入一個用戶定義的空閒任務,用於通知系統進入待機模式或者控制顯示待機界面。 完整的流程以下框架

IdelTask onStart -> IdleTask onStop -> Task onStart -> Task onPause -> NewTask onStart -> NewTask onStop -> Task onResume -> Task onStop -> IdleTask onStartasync

2. 當前任務可打斷不可恢復時

當前任務不可恢復時,不會走 onPause,onResume,流程以下: IdelTask onStart -> IdleTask onStop -> Task onStart -> Task onStop -> NewTask onStart -> NewTask onStop -> IdleTask onStart函數

3.當前任務可恢復但不可打斷時

當前任務可恢復但不可打斷,可是用戶又想要當即執行新任務時,經過接口通知用戶選擇馬上結束或暫停當前任務仍是保持現狀。保持現狀則新任務只能等到當前任務自行結束後自行,不然,將暫停或者結束當前任務,而後走如上的流程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 時釋放。

經過調用 TaskPoolmainLoop() 執行任務列表的調度流程,具體流程見代碼,爲了不多線程開銷,這裏用了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)  //掛起防止線程堵塞
}

經過調用 TaskPooladdNewTask() 通知TaskPool執行新的任務。

須要注意

waitingTask含有多個任務時

須要根據新任務的優先級插入到合適的位置。

當前任務不可打斷時

經過 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釋放時釋放。

  1. add()往隊列了添加子任務
  2. start() 開始運行隊列中的子任務
  3. forceCancel() 是取消剩下的子任務,清空隊列
  4. pauseSubTasks() 是在任務 onPause時暫停隊列運行
  5. 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()處掛起。

相關文章
相關標籤/搜索