在开发中,我们遇到了需要获取多个异步读取的结果才能继续执行的逻辑。
这时我们设计一个异步读取队列类,它负责收集所有需要异步读取的命令,并且在所有的异步读取命令完成之后,按照指定的顺序返回结果,执行相应的回调。
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
方法添加的是一个以TaskQueue
和int
为参数的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); } }
|
注意TaskQueueMgr
的AddReaderQueue
方法,它将一个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() { 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(); } }
|