在C#编程中,协程的核心特性是能够暂停当前执行流程,在后续某个时机恢复执行,这种特性非常适合处理需要分步完成的异步任务,比如资源加载、定时逻辑、多步骤业务流程等。通过yield关键字结合迭代器方法,我们可以用原生C#语法模拟出协程的基础能力,不需要引入额外的框架依赖。

yield模拟协程的核心原理
yield关键字在C#中用于定义迭代器方法,当方法执行到yield return语句时,会暂停当前方法的执行,将控制权返回给调用方,同时保存当前方法的执行状态,包括局部变量的值、执行到的代码位置等。下次调用迭代器的MoveNext方法时,方法会从之前暂停的位置继续执行,这个特性和协程的暂停恢复逻辑完全吻合。
我们可以把需要分步执行的协程逻辑放到迭代器方法中,每一步执行完成后用yield return返回,外部通过一个驱动逻辑不断调用迭代器的MoveNext方法,就可以实现协程的逐步执行效果。
基础实现示例
下面是一个简单的协程模拟示例,实现了一个分步执行的任务流程,每一步之间可以插入等待逻辑:
using System;
using System.Collections;
// 协程驱动类,负责管理协程的执行
public class CoroutineRunner
{
private IEnumerator _coroutine;
public CoroutineRunner(IEnumerator coroutine)
{
_coroutine = coroutine;
}
// 执行协程的下一步,返回true表示协程还未执行完
public bool Update()
{
if (_coroutine == null) return false;
return _coroutine.MoveNext();
}
}
public class CoroutineDemo
{
// 用yield定义的迭代器方法,模拟协程逻辑
public static IEnumerator MyCoroutine()
{
Console.WriteLine("协程开始执行,第一步:初始化资源");
// 暂停协程,返回等待标记,这里可以自定义等待逻辑
yield return null;
Console.WriteLine("协程第二步:处理核心逻辑");
yield return null;
Console.WriteLine("协程第三步:收尾工作");
yield return null;
Console.WriteLine("协程执行完成");
}
public static void Main()
{
// 创建协程实例
IEnumerator coroutine = MyCoroutine();
// 创建协程驱动
CoroutineRunner runner = new CoroutineRunner(coroutine);
// 模拟每帧执行协程的下一步,这里用循环模拟帧更新逻辑
while (runner.Update())
{
// 这里可以添加帧间隔等待逻辑,比如Thread.Sleep(16)模拟60帧
}
}
}
带等待条件的协程实现
实际开发中,协程往往需要等待特定条件满足后再继续执行,比如等待一定时间、等待某个资源加载完成等。我们可以自定义等待类型,让yield return返回对应的等待对象,驱动逻辑根据返回的对象判断是否继续推进协程。
using System;
using System.Collections;
// 等待指定帧数的等待类
public class WaitForFrames
{
public int RemainingFrames { get; private set; }
public WaitForFrames(int frameCount)
{
RemainingFrames = frameCount;
}
// 每帧调用,返回true表示等待还未结束
public bool Tick()
{
RemainingFrames--;
return RemainingFrames > 0;
}
}
public class AdvancedCoroutineRunner
{
private IEnumerator _coroutine;
private object _currentWaitObj;
public AdvancedCoroutineRunner(IEnumerator coroutine)
{
_coroutine = coroutine;
}
public bool Update()
{
if (_coroutine == null) return false;
// 如果当前有等待对象,先处理等待逻辑
if (_currentWaitObj != null)
{
if (_currentWaitObj is WaitForFrames waitForFrames)
{
if (waitForFrames.Tick()) return true; // 等待未结束,继续等待
}
else if (_currentWaitObj != null)
{
// 其他等待类型的处理逻辑
return true;
}
_currentWaitObj = null;
}
// 推进协程到下一步
bool hasNext = _coroutine.MoveNext();
if (hasNext)
{
// 保存当前yield返回的对象,用于下次Update判断
_currentWaitObj = _coroutine.Current;
return true;
}
return false;
}
}
public class AdvancedCoroutineDemo
{
public static IEnumerator TimedCoroutine()
{
Console.WriteLine("开始等待3帧");
// 返回等待3帧的对象
yield return new WaitForFrames(3);
Console.WriteLine("3帧等待结束,执行后续逻辑");
yield return null;
Console.WriteLine("协程全部执行完成");
}
public static void Main()
{
IEnumerator coroutine = TimedCoroutine();
AdvancedCoroutineRunner runner = new AdvancedCoroutineRunner(coroutine);
int frame = 0;
while (runner.Update())
{
frame++;
Console.WriteLine($"当前帧数:{frame}");
}
}
}
实现注意事项
- 迭代器方法中的局部变量状态会被自动保存,不需要手动维护,但是如果是引用类型的外部变量,需要注意修改的时机,避免状态混乱。
- yield return返回的对象可以根据需求自定义,只要驱动逻辑能够识别并处理即可,扩展性比较强。
- 这种模拟方式实现的协程本质是单线程的,所有逻辑都在同一个线程中分步执行,不会出现多线程的并发问题,但是也不能用来处理耗时的CPU密集型任务,否则会阻塞驱动协程的线程。
- 如果需要在Unity等游戏引擎中使用,引擎本身已经提供了完善的协程支持,不需要自己实现,本文的方法更适合没有内置协程支持的普通C#应用场景。