介绍
Unity 中的协程本质上是一个状态机,使用 C# yield 语法实现的一个迭代器,在启动协程时将其加入到管理器中,在合适的时机不断调用。
环境
动机
由于原有的资源管理代码使用协程处理资源加载问题,因此会导致资源管理协程与逻辑代码协程执行先后顺序不确定。
如果资源管理协程在逻辑代码协程之后执行,就会出现资源加载后未设置就显示并直到下一帧才设置更新,出现一帧错误的画面。
因此需要将资源管理协程的执行顺序手动移动到逻辑代码协程之前,为了最大程度减少对原有资源管理代码的改动,尝试使用自定义协程管理器来驱动原有资源管理协程。
需求
嵌套
支持协程嵌套,即 yield return AnotherCoroutine();
实现时可以考虑使用递归方法调用与自行维护协程栈两种方法。
Unity 协程类
支持 Unity 自带 YieldInstruction 协程,如 WWW LoadAssetAsync LoadAssetBundleAsync
实现时需要对其进行特殊处理,因为 YieldInstruction 不是 IEnumerator,必须手动处理。
考虑并未使用 YieldInstruction(如:WaitForSeconds),而只是使用了 AsyncOperation 这个子类,因此可以直接使用子类的 isDone 属性。
实现
管理器
CustomCoroutineManager.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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
  | 
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CustomCoroutineManager
{
    private readonly HashSet<CoroutineWrapper> currentIterators;
    private readonly List<CoroutineWrapper> iteratorsToAdd;
    private readonly List<CoroutineWrapper> iteratorsToRemove;
    public CustomCoroutineManager()
    {
        currentIterators = new HashSet<CoroutineWrapper>();
        iteratorsToAdd = new List<CoroutineWrapper>();
        iteratorsToRemove = new List<CoroutineWrapper>();
    }
    public CoroutineWrapper StartCoroutine(IEnumerator enumerator)
    {
        var iterator = new CoroutineWrapper(enumerator);
        iteratorsToAdd.Add(iterator);
        return iterator;
    }
    public void StopCoroutine(CoroutineWrapper coroutineWrapper)
    {
        if (coroutineWrapper != null)
        {
            iteratorsToRemove.Add(coroutineWrapper);
        }
    }
    public bool Update()
    {
        if (iteratorsToAdd.Count > 0)
        {
            // 添加
            iteratorsToAdd.ForEach(it => { currentIterators.Add(it); });
            iteratorsToAdd.Clear();
        }
        if (currentIterators.Count > 0)
        {
            foreach (var i in currentIterators)
            {
                i.Update();
            }
            currentIterators.RemoveWhere(i => i.isEnd);
        }
        if (iteratorsToRemove.Count > 0)
        {
            // 删除
            iteratorsToRemove.ForEach(it => { currentIterators.Remove(it); });
            iteratorsToRemove.Clear();
        }
        return currentIterators.Count > 0;
    }
    public class CoroutineWrapper
    {
        public bool isEnd { get; private set; }
        readonly IEnumerator _enumerator;
        public CoroutineWrapper(IEnumerator enumerator)
        {
            _enumerator = enumerator;
            isEnd = _enumerator == null;
        }
        public void Update()
        {
            isEnd = !MoveNext(_enumerator);
        }
        private bool MoveNext(IEnumerator enumerator)
        {
            //yield return 另一个协程:递归 MoveNext
            var child = enumerator.Current as IEnumerator;
            if (child != null && MoveNext(child))
                return true;
            // 处理 Unity 内置协程:WaitForSeconds LoadAssetAsync
            var asyncOperation = enumerator.Current as AsyncOperation;
            if (asyncOperation != null && !asyncOperation.isDone)
                return true;
            if (enumerator.MoveNext())
                return true;
            return false;
        }
    }
}
  | 
 
使用方法
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
  | 
private readonly CustomCoroutineManager _customCoroutineManager = new CustomCoroutineManager();
void Start()
{
    _customCoroutineManager.StartCoroutine(DoSomething());
}
void Update()
{
    _customCoroutineManager.Update();
}
IEnumerator DoSomething()
{
    Debug.Log("1");
    yield return new WaitForSeconds(1f);
    Debug.Log("2");
}
  | 
 
参考资料
使用协程栈方法处理嵌套协程,同时处理了 WaitForSeconds 这种非 AsyncOperation 子类。
递归方法处理嵌套协程
自定义包装协程类