在完善轻量地图组件的异步网络能力时,我遇到的核心问题并不是“怎样发出一个异步请求”,而是请求完成以后,怎样安全地把结果交还给地图引擎。

网络回调可能运行在协议栈线程,地图状态却通常要求在固定任务线程中串行修改。如果直接从网络线程进入地图引擎,短期内可能看不出问题,但在并发请求、页面退出或重复回调时,很容易出现数据竞争和生命周期错误。

地图线程在框架中的作用

轻量地图内部会同时维护视图状态、瓦片缓存、覆盖物、搜索结果和渲染任务。为每个对象分别加锁不仅复杂,也很难验证锁顺序。

框架采用的思路是:工作线程可以负责网络和文件等耗时操作,但涉及地图核心状态的操作统一回到地图任务线程。

网络线程接收结果
    → 记录请求终态
    → 非阻塞投递短任务
    → 地图任务线程分发回调
    → 地图引擎继续处理

地图任务线程并不等于 UI 线程。它的职责是串行化地图引擎调用;如果最终还要修改界面,仍需遵守 UI 框架自己的线程要求。

我做的主要调整

1. 向能力提供层注入任务投递器

我没有让网络适配代码直接依赖某个 RTOS 的队列接口,而是通过初始化上下文向下层提供一个小型任务投递器。

投递器只表达几个基本语义:

  • 投递是异步和非阻塞的;
  • 返回成功只表示任务已经进入队列;
  • 任务最终在地图任务线程执行;
  • 未初始化、正在退出或队列已满时允许返回失败。

这样,网络适配层只知道“把短任务交给地图线程”,不需要知道地图任务的具体实现。

2. 缩小网络线程职责

网络线程只完成接收数据、更新请求状态和记录终态,不在持有网络上下文时调用地图 SDK。

成功、失败、取消和超时最终都会转换为一次地图任务。地图任务再按照既定顺序分发响应头、响应体和终态回调,避免不同线程同时进入地图引擎。

3. 为每个请求维护独立上下文

异步请求不能继续复用同步模式下的共享响应缓冲区。每个请求都需要独立保存请求标识、响应数据、取消状态和最终结果。

这项修改解决了两个问题:

  • 多个请求并发时不会互相覆盖结果;
  • 每个请求只能进入一次终态,避免成功和取消重复通知。

同时,响应缓冲区必须设置上限。异步并不意味着可以无限累积数据,资源受限设备尤其需要在边界处拒绝异常响应。

任务参数所有权比投递本身更重要

把一个函数指针放入队列很简单,真正容易出错的是参数生命周期。

如果工作线程把栈变量地址投递出去,函数返回后,这个地址可能已经失效。比较稳妥的做法是为任务准备独立上下文,并明确以下规则:

  • 入队成功后,参数由谁持有;
  • 任务执行后由谁释放;
  • 入队失败时由谁回收;
  • 退出时丢弃任务是否需要清理;
  • 同一个参数是否可能被释放两次。

我在梳理异步链路时,始终把“成功执行”和“退出丢弃”当作同等重要的路径。只处理正常完成,往往会把内存泄漏或退出后回调留到后期。

队列满时不能改成无限等待

地图任务队列是有界资源。网络短时间返回大量结果时,投递失败是合理状态,不应假设它永远不会发生。

不同事件可以使用不同策略:

事件类型处理方式
可覆盖的状态保留最新值,丢弃中间状态
高频位置或姿态合并任务,避免无界排队
必须送达的终态记录在请求上下文中,进行有界补投
不可重复操作使用请求 ID 和状态机防止重复消费

不建议让网络线程一直阻塞等待队列空位。它可能正持有协议栈资源,长时间等待反而会放大拥塞。

同线程重入带来的死锁

代理层的同步调用通常采用“投递任务并等待结果”的方式。如果当前调用本来就在地图任务线程,再次投递后同步等待,就会形成当前任务等待自己消费队列的死锁。

为解决这个问题,我在任务运行器中记录实际地图任务 ID。同步代理调用先判断当前线程:

  • 已经位于地图任务线程时,直接执行真实操作;
  • 来自其他线程时,继续投递并等待;
  • 普通异步任务仍然入队,避免形成过深的回调重入。

任务 ID 是跨线程共享状态,不能只使用 volatile。它需要原子读写或锁来建立明确的可见性关系。不过,一个原子字段只能解决任务 ID 的同步,不能自动保证启动、停止、投递和析构的整体安全。

退出流程必须先切断回调

地图页面退出时,最危险的情况是对象已经销毁,网络请求却仍在完成并投递任务。

比较清晰的退出顺序是:

  1. 停止接受新的外部请求;
  2. 标记投递器进入停止状态;
  3. 取消或隔离仍在进行的网络请求;
  4. 处理或丢弃队列中的剩余任务;
  5. 等待不会再产生地图回调;
  6. 最后销毁地图对象和任务运行器。

进入停止状态后,新投递必须立即失败。队列中的旧任务选择排空还是丢弃都可以,但必须有统一规则,并保证参数只释放一次。

验证时我重点关注什么

除了正常请求,我还会检查以下场景:

  • 多个请求同时完成;
  • 队列满导致投递失败;
  • 请求在完成前被取消;
  • 页面退出和网络回调同时发生;
  • 地图线程内重入同步代理接口;
  • 无效响应不能被当作业务成功;
  • 反初始化完成后不再触发地图回调;
  • 每个请求只产生一次最终通知。

其中“传输成功”和“响应有效”也要分开。HTTP 成功但响应体为空或不符合约定格式时,应进入明确的格式错误路径,而不是继续回调成功。

对框架的一点理解

经过这次调整,我认为任务投递器并不是一个单纯的线程工具,它实际上是公共框架和能力提供层之间的线程契约。

上层通过代理获得统一的串行调用体验,下层只负责产生结果,任务运行器负责把结果带回正确线程。所有权、队列满和退出协议,则共同决定这条链路能否在异常情况下仍然可靠。

总结

这次异步改造的重点,是把网络线程和地图线程的职责重新划清:网络线程产生结果,地图线程消费结果,代理层处理同步调用和重入,生命周期协议保证退出后不再回调。

对于轻量地图组件来说,一个小而明确的非阻塞投递接口,比让每个适配模块自行处理线程切换更容易维护,也更适合后续扩展定位、文件解码和传感器等异步数据源。

Logo

社区规范:仅讨论OpenHarmony相关问题。

更多推荐