CSharp的协程实现与原理

Reference

【迭代器模式】深入理解协程 - 知乎 (zhihu.com)
Unity协程的原理与应用 - 知乎 (zhihu.com)
[C#进阶]C#实现类似Unity的协程 - 知乎 (zhihu.com)

概述

引用自:Unity协程的原理与应用 - 知乎 (zhihu.com)

函数调用的本质是压栈,协程的唤醒也一样,调用IEnumerator.MoveNext()时会把协程方法体压入当前的函数调用栈中执行,运行到yield return后再弹栈。

在应用层面,协程将一个循环执行的概念变化成为一个顺序执行的概念,并且可以等待一个阻塞任务的执行而不卡死主线程,当然如果在协程里面写死循环还是会卡死主线程。因为协程本质上还是在主线程中执行的。

图1 协程的三大将

了解协程我们首先要了解迭代器,引用自:【迭代器模式】深入理解协程 - 知乎 (zhihu.com)

迭代器(iterator)有时又称光标(cursor)是程序设计的软件设计模式,可在容器对象(container,例如链表或数组)上遍访的接口,设计人员无需关心容器对象的内存分配的实现细节。

可以理解为对一个链表遍历,每次调用MoveNext相当于移动到这个链表的子元素,但是这不仅仅可以是一个链表,可以是数组、字典巴拉巴拉,是任何继承IEnumerator的类。

1
2
3
4
5
6
7
8
9
10
11
12
using System.Runtime.InteropServices;

namespace System.Collections{
[ComVisible(true)]
[Guid("496B0ABF-CDEE-11d3-88E8-00902754C43A")]
public interface IEnumerator{
object Current { get; }

bool MoveNext();
void Reset();
}
}

迭代器中,调用MoveNext可以执行到下一个yiled return,current就是yiled retuen 的值。

关于yield要更加深入了解一点,可以看看深入理解C#,其中第六章6.1的标题是6.1:手写迭代器的痛苦yield就是为了简化迭代器的书写的语法糖。

实现

在了解了迭代器是什么,之后其实我们的目标就非常明确了:我们要在主线程上每一帧对所有迭代器进行迭代。这样在高速的CPU运行速度下,就像在并行一样。

但是为了更好的书写等到逻辑我们还要实现CoroutineIWait

Coroutine是对迭代器的一个包装,通过这个Coroutine可以由我们自己决定到底是等待还是IEnumerator.MoveNext

IWait是等到的接口,继承这个接口必须要实现public bool Tick(float dt);函数来返回当前是否等待结束。

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
namespace 实现协程  
{
public interface IWait
{
public bool Tick(float dt);
}

public class WaitForSeconds : IWait
{
private float m_waitTime;

public WaitForSeconds(float time)
{
m_waitTime = time;
}

public bool Tick(float dt)
{
m_waitTime -= dt;
return m_waitTime <= 0;
}
}

public class WaitForNextFrame : IWait
{
private int m_frameCount;

public WaitForNextFrame()
{ m_frameCount = 1; }
public bool Tick(float dt)
{
m_frameCount -= 1;
return m_frameCount <= 0;
}
}

public class WaitForFrame : IWait
{
private int m_frameCount;

public WaitForFrame(int frameCount)
{
m_frameCount = frameCount;
}

public bool Tick(float dt)
{
m_frameCount -= 1;
return m_frameCount <= 0;
}
}
}
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
using System.Collections;  

namespace 实现协程
{
public class Coroutine
{
private IEnumerator m_enumerator;

public Coroutine(IEnumerator enumerator)
{
m_enumerator = enumerator;
}

public bool MoveNext(float dt)
{
if (m_enumerator == null)
{
return false;
}

//看下有没有等待接口
var waitComplete = false;

//空无需等待
if (m_enumerator.Current == null)
{
waitComplete = true;
}
else
{
var wait = m_enumerator.Current as IWait;
if (wait != null)
{
waitComplete = wait.Tick(dt);
}
}

if (waitComplete)
{
//我等待时间结束了 可以迭代下一个元素了
return m_enumerator.MoveNext();
}
else
{
//我还要继续等待 告诉管理器我下一个循环还要走 但是本身没有迭代
return true;
}
}

public void Stop()
{
m_enumerator = null;
}
}
}

最后是协程管理器,管理所有的协程,通过OnUpdate函数,每帧都迭代所有协程,协程本身能否在本帧迭代,取决于m_enumerator.Current是不是IWait

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
using System.Collections;  
using System.Collections.Generic;
using TMPro;
using Unity.VisualScripting;

namespace 实现协程
{
public class CoroutineMgr
{
public LinkedList<Coroutine> m_coroutines;
public LinkedList<Coroutine> m_coroutinesToStop;

public CoroutineMgr()
{ m_coroutines = new LinkedList<Coroutine>(); m_coroutinesToStop = new LinkedList<Coroutine>(); }
public Coroutine StartCoroutine(IEnumerator enumerator)
{
var coroutine = new Coroutine(enumerator);
m_coroutines.AddLast(coroutine);
return coroutine;
}

public void StopCoroutine(Coroutine coroutine)
{
m_coroutinesToStop.AddLast(coroutine);
}

public void OnUpdate(float dt)
{
var node = m_coroutines.First;
while (node != null)
{
bool wantMoveNext = false;
var coroutine = node.Value;

if (!m_coroutinesToStop.Contains(coroutine))
{
wantMoveNext = coroutine.MoveNext(dt);
}

if (!wantMoveNext)
{
m_coroutines.Remove(node);
}

node = node.Next;
}
}
}
}