封面来自:https://www.youtube.com/watch?app=desktop&v=VXJSJ6c3ZIs

前言

最近实践了很多有关异步记载和使用的业务场景,无一例外的,由于 异步 的存在,业务代码需要非常小心的维护各种状态,才能足够稳定和安全

只遇到一次还好,代码和人有一个能跑就行,遇到很多次心情就很烦躁了,所以准备抽空研究下这种业务场景较为通用的解决方案

正文

场景例程

  • 整个世界被拆分成无数个nxn的tile,需要根据相机位置动态加载,卸载tile(以下简称地图流式行为
  • 相机位置每帧都在变动,即每帧都有可能触发地图流式行为,正常视角渲染所需tile数量在32~50之间
  • 出于表现和内存性能的平衡考虑,最大支持同时存在256个加载的tile,基于LRU策略进行调度
  • tile的加载是异步的
  • 最终需要把加载的tile资源按照正确的索引上传至GPU

问题展示

直接上代码,我们以一帧的LateUpdate为例(此LateUpdate每帧执行),将资源加载和使用看为一个整体

但由于异步的特性,当某一帧的异步完成时,一些数据和状态可能已经发生了变动,会影响到另一帧的状态,而我们这里又只维护了本帧的任务状态,所以需要额外更新一次数据,将其融入到全局的状态表中,才能保证每一次异步的处理都是正确的,而不会因为异步时序的不确定性导致冲突

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public async UniTaskVoid LateUpdate()
{
// 收集需要加载的资源
CollectLoadResTask();

// LRU策略处理资源占用,例如我们需要记录一个加载资源对应的tile索引信息
LRUCache.Process();

// 创建加载任务
tasks = CreateTasks();

// 维护更新数据,记录此次加载所有task信息,用于异步完成后匹配
ProcessData();

// 异步加载所有资源
await UniTask.WhenAll(tasks);

// 所有资源加载完毕,由于tile索引信息可能已变动,所以需要检测资源是否还被需要
if(CheckResValid(tasks))
{
// 上传至GPU
UpLoadToGPU();
}
}

而生产者-消费者模型可以很好的规划和解耦此类问题

解决方案

简单来说就是解耦生产(加载)和消费(上传GPU)流程,使得生产只处理生产,消费只处理消费

代码中特殊标识出来的行表示数据处理是全局的生产者队列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
private async UniTaskVoid Produce()
{
// 收集需要加载的资源
CollectLoadResTask();

// LRU策略处理资源占用,例如我们需要记录一个加载资源对应的tile索引信息
LRUCache.Process();

// 创建加载任务
tasks = CreateTasks();

// 异步加载所有资源
await UniTask.WhenAll(tasks);

// ==== 写入产品流水线 ====
WriteToProductQueue();
}

private void Consume()
{
// ==== 从产品流水线获取内容 ====
product = ReadFromProductQueue();

// 由于tile索引信息可能已变动,所以需要检测资源是否还被需要
if(CheckResValid(product))
{
// 上传至GPU
UpLoadToGPU();
}

// ==== 从产品队列移除 ====
RemoveFromProductQueue(product);
}

// 整个过程不再存在异步等待行为,而是由生产和消费者自行维护
public void LateUpdate()
{
Produce();
Consume();
}

至此,我们将维护的状态对象从每帧的异步内容转换为全局的生产者队列

总结

其实大家可以看到,两种实现方式代码差别并不大,只是把维护的目标数据从一些复杂的状态转换成单一的,全局生产者队列而已,换句话说,我们当然也可以将原本的代码优化成只维护一份全局的数据状态的形式来达成同样的效果

这更多的是一种思路和理解成本上的优化,这对于程序的迭代是大有裨益的

参考

C# Channels - Publish / Subscribe Workflows