在开发中,我们遇到了需要获取多个异步读取的结果才能继续执行的逻辑。

这时我们设计一个异步读取队列类,它负责收集所有需要异步读取的命令,并且在所有的异步读取命令完成之后,按照指定的顺序返回结果,执行相应的回调。

TaskQueue

在Module文件夹内,新建TaskQueue文件夹并新建TaskQueue.cs文件

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
using System;
using System.Collections.Generic;
using UnityEngine;

public class TaskQueue
{
private readonly Queue<Action<TaskQueue, int>> _tasks;
private int _id;
private object[] _values;
private int _addValueTimes;
private Action<object[]> _onComplete;

public TaskQueue()
{
_tasks = new();
ResetData();
}
private void ResetData()
{
_id = -1;
_addValueTimes = 0;
}
public void Add(Action<TaskQueue, int> action)
{
_tasks.Enqueue(action);
}
public void Execute(Action<object[]> onComplete)
{
_onComplete = onComplete;
_values = new object[_tasks.Count];
while (_tasks.Count > 0)
{
_id++;
Action<TaskQueue, int> action = _tasks.Dequeue();
action?.Invoke(this,_id);
}
ResetData();
}
public void AddValue(int id, object value)
{
_addValueTimes++;
_values[id] = value;
JudgeComplete();
}
private void JudgeComplete()
{
if(_addValueTimes == _values.Length)
{
_onComplete?.Invoke(_values);
}
else if( _addValueTimes > _values.Length)
{
Debug.LogError("TaskQueue.AddValue执行次数过多");
}
}
}

这里的Add方法添加的是一个以TaskQueueint为参数的Action。其中的int参数表示的是方法执行的顺序的id,TaskQueue会根据这个id按照顺序返回结果。

下面的Execute方法就是Add方法传入的函数执行的地方。将Queue里面的函数依次执行,每次进行id递增。

AddValue方法用来存储异步执行的所有结果,存放在object数组内。

JudgeComplete方法用来判断所有的结果是否已经都获取到了,如果成功获取完毕,执行返回所有结果的回调。

TaskQueue泛型类

如果返回结果都是同一种类型,可以使用TaskQueue<T>泛型类,避免装箱拆箱。

直接在TaskQueue.cs文件内写

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
41
42
43
44
45
46
47
48
49
50
51
52
public class TaskQueue<T>
{
private readonly Queue<Action<TaskQueue<T>, int>> _tasks;
private int _id;
private T[] _values;
private int _addValueTimes;
private Action<T[]> _onComplete;

public TaskQueue()
{
_tasks = new();
ResetData();
}
private void ResetData()
{
_id = -1;
_addValueTimes = 0;
}
public void Add(Action<TaskQueue<T>, int> action)
{
_tasks.Enqueue(action);
}
public void Execute(Action<T[]> onComplete)
{
_onComplete = onComplete;
_values = new T[_tasks.Count];
while (_tasks.Count > 0)
{
_id++;
Action<TaskQueue<T>, int> action = _tasks.Dequeue();
action?.Invoke(this, _id);
}
ResetData();
}
public void AddValue(int id, T value)
{
_addValueTimes++;
_values[id] = value;
JudgeComplete();
}
private void JudgeComplete()
{
if (_addValueTimes == _values.Length)
{
_onComplete?.Invoke(_values);
}
else if (_addValueTimes > _values.Length)
{
Debug.LogError("TaskQueue.AddValue执行次数过多");
}
}
}

TaskQueueMgr(难中之难)

TaskQueueMgr也对应了一个普通实现和一个泛型实现。

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
41
42
43
44
45
46
using System;

public class TaskQueueMgr<T> : NormalSingleton<TaskQueueMgr<T>>
{
private readonly TaskQueue<T> _queue;
public TaskQueueMgr()
{
_queue = new TaskQueue<T>();
}
public void AddReaderQueue(Func<IReader> getReader)
{
_queue.Add((self, id) =>
{
getReader.Invoke().Get<T>(data =>
{
self.AddValue(id, data);
});
});
}
public void Execute(Action<T[]> onComplete)
{
_queue.Execute(onComplete);
}
}
public class TaskQueueMgr : NormalSingleton<TaskQueueMgr>
{
private readonly TaskQueue _queue;
public TaskQueueMgr()
{
_queue = new();
}
public void AddReaderQueue<T>(Func<IReader> getReader)
{
_queue.Add((self, id) =>
{
getReader.Invoke().Get<T>(data =>
{
self.AddValue(id, data);
});
});
}
public void Execute(Action<object[]> onComplete)
{
_queue.Execute(onComplete);
}
}

注意TaskQueueMgrAddReaderQueue方法,它将一个Func<IReader>作为参数,我们通过一个Lambda表达式来传递reader,这是因为AddReaderQueue传入的任何参数都是要延时执行的,如果不使用Lambda表达式,reader.Get方法在执行时,总是会调用单一的reader引用,然后导致后面的读取任务获取到的reader还是最开始的reader。

使用Func包装一下reader,相当于不同的异步方法执行时,调用不同的函数指针,这样参数的引用就不会被影响,这是一个很巧妙的思想,要仔细体会。

修改ViewBase

当一个UI界面在InitChild时使用TaskQueue加载子界面,就会导致UI执行的时序出现问题,子界面还没加载出来,Init方法就已经执行完了。

修改IView,添加Reacquire方法,也就是再次初始化的API

1
2
3
4
5
public interface IView : IViewUpdate, IViewInit, IViewShow, IViewHide
{
UnityEngine.Transform GetTrans();
void Reacquire();
}

修改ViewBase

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
using System.Collections.Generic;
using UnityEngine;

public abstract class ViewBase : MonoBehaviour, IView
{
//...
private void GetAllSubView()//+++
{
_viewInits = new List<IViewInit>();
_viewShows = new List<IViewShow>();
_viewHides = new List<IViewHide>();
_viewUpdates = new List<IViewUpdate>();
InitInterface();
}
private void InitInterface()//+++
{
InitViewInterface(_viewInits);
InitViewInterface(_viewShows);
InitViewInterface(_viewHides);
InitViewInterface(_viewUpdates);
}
public void Reacquire()//+++
{
InitInterface();
InitAllSubView();
}
//...
}

修改ControllerBase

ViewBase一样,ControllerBase也需要添加再次初始化的逻辑。

先修改IController

1
2
3
4
5
public interface IController : IControllerInit, IControllerShow, IControllerHide, IControllerUpdate
{
void AddUpdateListener(System.Action update);
void Reacquire();
}

修改ControllerBase

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
public abstract class ControllerBase : MonoBehaviour, IController
{
//...
public void Reacquire()//TODO: 没有再次调用AddUpdateAction
{
InitInterface();
InitComponents();
}
private void InitInterface()
{
InitComponent(_inits, this);
InitComponent(_shows, this);
InitComponent(_hides, this);
InitComponent(_updates, this);
}
private void InitAllComponents()
{
_inits = new List<IControllerInit>();
_shows = new List<IControllerShow>();
_hides = new List<IControllerHide>();
_updates = new List<IControllerUpdate>();
InitInterface();
}
//...
}