GameFramwork学习(四):任务池(Task Pool)

GameFramwork学习(四):任务池(Task Pool) 文章目录前言一、TaskBase二、ITaskAgentT三、TaskPoolT1.Agent管理1.添加Agent2.Task管理1.添加Task2.移除Task3.执行任务1.正在执行的任务2.等待执行的任务总结前言任务池顾名思义是将各种任务在 GameFramework 中有资源加载、网络请求、下载放到任务池里统一管理执行。一、TaskBase任务基类任务池管理的具体对象。/// summary/// 任务基类。/// /summaryinternalabstractclassTaskBase:IReference{/// summary/// 任务默认优先级。/// /summarypublicconstintDefaultPriority0;// 任务编号privateintm_SerialId;// 任务标签privatestringm_Tag;// 任务优先级privateintm_Priority;// 任务用户自定义数据privateobjectm_UserData;// 任务是否完成privateboolm_Done;/// summary/// 获取任务描述。/// /summarypublicvirtualstringDescription{get{returnnull;}}}TaskBase 继承了 IReference 接口创建出的 Task 需要接受引用池的管理除了任务的额外信息字段属性任务标签Tag任务用户自定义数据UserData任务描述Description主要就是执行过程中需要用到的任务编号SerialId任务优先级Priority任务是否完成Done需要使用到任务池来管理执行的继承任务基类扩展构建自定义任务的 Task 类二、ITaskAgent任务池本身不会具体处理任务真正负责任务具体处理逻辑的是任务池中的代理Agent一个代理处理一个任务。/// summary/// 任务代理接口。/// /summary/// typeparam nameT任务类型。/typeparaminternalinterfaceITaskAgentTwhereT:TaskBase{/// summary/// 获取任务。/// /summaryTTask{get;}/// summary/// 初始化任务代理。/// /summaryvoidInitialize();/// summary/// 任务代理轮询。/// /summary/// param nameelapseSeconds逻辑流逝时间以秒为单位。/param/// param namerealElapseSeconds真实流逝时间以秒为单位。/paramvoidUpdate(floatelapseSeconds,floatrealElapseSeconds);/// summary/// 关闭并清理任务代理。/// /summaryvoidShutdown();/// summary/// 开始处理任务。/// /summary/// param nametask要处理的任务。/param/// returns开始处理任务的状态。/returnsStartTaskStatusStart(Ttask);/// summary/// 停止正在处理的任务并重置任务代理。/// /summaryvoidReset();}创建任务池时要先创建一些代理代理数量可自定义任务池靠这些代理去处理任务当前的任务Task代理初始化Initialize()代理执行的过程经历以下生命周期Start(T task)Update(float elapseSeconds, float realElapseSeconds)Shutdown()Reset()其中开始处理Start时会返回任务的状态StartTaskStatus因为任务并不一定会立即执行Done可以立刻处理完成此任务CanResume可以继续处理此任务HasToWait不能继续处理此任务需等待其它任务执行完成UnknownError不能继续处理此任务出现未知错误需要使用到任务池来管理执行的继承代理接口实现自定义任务的具体处理逻辑三、TaskPool通过上面的介绍我们知道了TaskPool管理着两类对象任务Task、执行任务的代理Agent。1.Agent管理/// summary/// 任务池。/// /summary/// typeparam nameT任务类型。/typeparaminternalsealedclassTaskPoolTwhereT:TaskBase{privatereadonlyStackITaskAgentTm_FreeAgents;privatereadonlyGameFrameworkLinkedListITaskAgentTm_WorkingAgents;/// summary/// 获取任务代理总数量。/// /summarypublicintTotalAgentCount{get{returnFreeAgentCountWorkingAgentCount;}}/// summary/// 获取可用任务代理数量。/// /summarypublicintFreeAgentCount{get{returnm_FreeAgents.Count;}}/// summary/// 获取工作中任务代理数量。/// /summarypublicintWorkingAgentCount{get{returnm_WorkingAgents.Count;}}}与对象池、引用池都类似由两个缓存区分别存储空闲的代理和工作中的代理FreeAgents、WorkingAgents为什么 FreeAgents 数据结构使用的StackWorkingAgents 用的却是LinkedList呢一些记录数量信息的属性空闲代理数量FreeAgentCount工作中代理数量WorkingAgentCount全部代理数量TotalAgentCount1.添加Agent/// summary/// 增加任务代理。/// /summary/// param nameagent要增加的任务代理。/parampublicvoidAddAgent(ITaskAgentTagent){if(agentnull){thrownewGameFrameworkException(Task agent is invalid.);}agent.Initialize();m_FreeAgents.Push(agent);}将Agent初始化以后添加进空闲代理队列FreeAgents只提供了添加接口没提供移除接口说明在 TaskPool 整个生命周期中不鼓励主动减少 Agent除了任务池本身被 Shutdown/// summary/// 关闭并清理任务池。/// /summarypublicvoidShutdown(){RemoveAllTasks();while(FreeAgentCount0){m_FreeAgents.Pop().Shutdown();}}2.Task管理/// summary/// 任务池。/// /summary/// typeparam nameT任务类型。/typeparaminternalsealedclassTaskPoolTwhereT:TaskBase{privatereadonlyGameFrameworkLinkedListTm_WaitingTasks;/// summary/// 获取等待任务数量。/// /summarypublicintWaitingTaskCount{get{returnm_WaitingTasks.Count;}}}只有一个缓存区存储等待中的任务WaitingTasks数据结构为LinkedList记录数量的属性等待中的任务数量WaitingTaskCount1.添加Task/// summary/// 增加任务。/// /summary/// param nametask要增加的任务。/parampublicvoidAddTask(Ttask){LinkedListNodeTcurrentm_WaitingTasks.Last;while(current!null){if(task.Prioritycurrent.Value.Priority){break;}currentcurrent.Previous;}if(current!null){m_WaitingTasks.AddAfter(current,task);}else{m_WaitingTasks.AddFirst(task);}将单个Task添加进等待任务队列WaitingTasks在Task添加时考虑到了优先级Priority的问题优先级大的放到链表前面先执行优先级小的后执行。2.移除Task/// summary/// 根据任务的序列编号移除任务。/// /summary/// param nameserialId要移除任务的序列编号。/param/// returns是否移除任务成功。/returnspublicboolRemoveTask(intserialId){foreach(Ttaskinm_WaitingTasks){if(task.SerialIdserialId){m_WaitingTasks.Remove(task);ReferencePool.Release(task);returntrue;}}LinkedListNodeITaskAgentTcurrentWorkingAgentm_WorkingAgents.First;while(currentWorkingAgent!null){LinkedListNodeITaskAgentTnextcurrentWorkingAgent.Next;ITaskAgentTworkingAgentcurrentWorkingAgent.Value;TtaskworkingAgent.Task;if(task.SerialIdserialId){workingAgent.Reset();m_FreeAgents.Push(workingAgent);m_WorkingAgents.Remove(currentWorkingAgent);ReferencePool.Release(task);returntrue;}currentWorkingAgentnext;}returnfalse;}/// summary/// 根据任务的标签移除任务。/// /summary/// param nametag要移除任务的标签。/param/// returns移除任务的数量。/returnspublicintRemoveTasks(stringtag){intcount0;LinkedListNodeTcurrentWaitingTaskm_WaitingTasks.First;while(currentWaitingTask!null){LinkedListNodeTnextcurrentWaitingTask.Next;TtaskcurrentWaitingTask.Value;if(task.Tagtag){m_WaitingTasks.Remove(currentWaitingTask);ReferencePool.Release(task);count;}currentWaitingTasknext;}LinkedListNodeITaskAgentTcurrentWorkingAgentm_WorkingAgents.First;while(currentWorkingAgent!null){LinkedListNodeITaskAgentTnextcurrentWorkingAgent.Next;ITaskAgentTworkingAgentcurrentWorkingAgent.Value;TtaskworkingAgent.Task;if(task.Tagtag){workingAgent.Reset();m_FreeAgents.Push(workingAgent);m_WorkingAgents.Remove(currentWorkingAgent);ReferencePool.Release(task);count;}currentWorkingAgentnext;}returncount;}/// summary/// 移除所有任务。/// /summary/// returns移除任务的数量。/returnspublicintRemoveAllTasks(){intcountm_WaitingTasks.Countm_WorkingAgents.Count;foreach(Ttaskinm_WaitingTasks){ReferencePool.Release(task);}m_WaitingTasks.Clear();foreach(ITaskAgentTworkingAgentinm_WorkingAgents){TtaskworkingAgent.Task;workingAgent.Reset();m_FreeAgents.Push(workingAgent);ReferencePool.Release(task);}m_WorkingAgents.Clear();returncount;}基本移除操作有以下几步从等待执行的任务WaitingTasks中查找如果有执行移除操作再从工作中的代理WorkingAgents中查找看正在处理的任务workingAgent.Task是否有如果有先重置并回收代理再移除任务前面提到 TaskBase继承了IReference所以移除任务时要多一步引用池释放引用操作。3.执行任务任务池通过Update每帧轮询执行任务运行逻辑是如果已经有正在执行的任务优先继续执行一次Update轮询再去考虑是否执行等待中的任务。/// summary/// 任务池轮询。/// /summary/// param nameelapseSeconds逻辑流逝时间以秒为单位。/param/// param namerealElapseSeconds真实流逝时间以秒为单位。/parampublicvoidUpdate(floatelapseSeconds,floatrealElapseSeconds){if(m_Paused){return;}ProcessRunningTasks(elapseSeconds,realElapseSeconds);ProcessWaitingTasks(elapseSeconds,realElapseSeconds);}1.正在执行的任务privatevoidProcessRunningTasks(floatelapseSeconds,floatrealElapseSeconds){LinkedListNodeITaskAgentTcurrentm_WorkingAgents.First;while(current!null){Ttaskcurrent.Value.Task;if(!task.Done){// 执行任务current.Value.Update(elapseSeconds,realElapseSeconds);currentcurrent.Next;continue;}LinkedListNodeITaskAgentTnextcurrent.Next;current.Value.Reset();m_FreeAgents.Push(current.Value);m_WorkingAgents.Remove(current);ReferencePool.Release(task);currentnext;}}遍历工作中的代理只要该代理处理的任务没有完成! Task.Done就继续执行如果任务完成了重置并回收代理直接释放 Task 的引用处理中的任务已经从 WaitingTasks 取出。2.等待执行的任务privatevoidProcessWaitingTasks(floatelapseSeconds,floatrealElapseSeconds){LinkedListNodeTcurrentm_WaitingTasks.First;while(current!nullFreeAgentCount0){ITaskAgentTagentm_FreeAgents.Pop();LinkedListNodeITaskAgentTagentNodem_WorkingAgents.AddLast(agent);Ttaskcurrent.Value;LinkedListNodeTnextcurrent.Next;// 开始执行任务StartTaskStatusstatusagent.Start(task);if(statusStartTaskStatus.Done||statusStartTaskStatus.HasToWait||statusStartTaskStatus.UnknownError){agent.Reset();m_FreeAgents.Push(agent);m_WorkingAgents.Remove(agentNode);}if(statusStartTaskStatus.Done||statusStartTaskStatus.CanResume||statusStartTaskStatus.UnknownError){m_WaitingTasks.Remove(current);}if(statusStartTaskStatus.Done||statusStartTaskStatus.UnknownError){ReferencePool.Release(task);}currentnext;}}遍历等待中的任务如果有空闲的代理FreeAgentCount 0才能处理当前的任务开始处理任务返回开始处理任务的状态状态是可以立即完成或未知错误Done/UnknownError重置并回收代理移除并释放该任务状态是需等待其他任务完成HasToWait只需重置并回收代理该任务并未从 WaitingTasks 中取出状态是可以继续执行CanResume将该任务从 WaitingTasks 取出即可。可以看到WorkingAgents、WaitingTasks会涉及到频繁的插入删除操作用LinkedList更好。总结GameFramework 任务池有良好的可扩展性控制 Agent 数量可以增加任务处理的并发量。