GFramework/docs/zh-CN/core/coroutine.md
GeWuYou fb14d7122c docs(style): 更新文档中的命名空间导入格式
- 将所有小写的命名空间导入更正为首字母大写格式
- 统一 GFramework 框架的命名空间引用规范
- 修复 core、ecs、godot 等模块的命名空间导入错误
- 标准化文档示例代码中的 using 语句格式
- 确保所有文档中的命名空间引用保持一致性
- 更新 global using 语句以匹配正确的命名空间格式
2026-03-10 07:18:49 +08:00

11 KiB
Raw Blame History

title, description
title description
协程系统 协程系统提供了轻量级的异步操作管理机制,支持时间延迟、事件等待、任务等待等多种场景。

协程系统

概述

协程系统是 GFramework 中用于管理异步操作的核心机制。通过协程,你可以编写看起来像同步代码的异步逻辑,避免回调地狱,使代码更加清晰易读。

协程系统基于 C# 的迭代器IEnumerator实现提供了丰富的等待指令YieldInstruction可以轻松处理时间延迟、事件等待、任务等待等各种异步场景。

主要特性

  • 轻量级协程调度器
  • 丰富的等待指令30+ 种)
  • 支持协程嵌套和组合
  • 协程标签和批量管理
  • 与事件系统、命令系统、CQRS 深度集成
  • 异常处理和错误恢复

核心概念

协程调度器

CoroutineScheduler 是协程系统的核心,负责管理和执行所有协程:

using GFramework.Core.Coroutine;

// 创建调度器(通常由架构自动管理)
var scheduler = new CoroutineScheduler(timeSource);

// 运行协程
var handle = scheduler.Run(MyCoroutine());

// 每帧更新
scheduler.Update();

协程句柄

CoroutineHandle 用于标识和控制协程:

// 运行协程并获取句柄
var handle = scheduler.Run(MyCoroutine());

// 检查协程是否存活
if (scheduler.IsCoroutineAlive(handle))
{
    // 停止协程
    scheduler.Stop(handle);
}

等待指令

等待指令YieldInstruction定义了协程的等待行为

public interface IYieldInstruction
{
    bool IsDone { get; }
    void Update(double deltaTime);
}

基本用法

创建简单协程

using GFramework.Core.Abstractions.Coroutine;
using GFramework.Core.Coroutine.Instructions;

public IEnumerator<IYieldInstruction> SimpleCoroutine()
{
    Console.WriteLine("开始");

    // 等待 2 秒
    yield return new Delay(2.0);

    Console.WriteLine("2 秒后");

    // 等待 1 帧
    yield return new WaitOneFrame();

    Console.WriteLine("下一帧");
}

使用协程辅助方法

using GFramework.Core.Coroutine;

public IEnumerator<IYieldInstruction> HelperCoroutine()
{
    // 等待指定秒数
    yield return CoroutineHelper.WaitForSeconds(1.5);

    // 等待一帧
    yield return CoroutineHelper.WaitForOneFrame();

    // 等待多帧
    yield return CoroutineHelper.WaitForFrames(10);

    // 等待条件满足
    yield return CoroutineHelper.WaitUntil(() => isReady);

    // 等待条件不满足
    yield return CoroutineHelper.WaitWhile(() => isLoading);
}

在架构组件中使用

using GFramework.Core.Model;
using GFramework.Core.Extensions;

public class PlayerModel : AbstractModel
{
    protected override void OnInit()
    {
        // 启动协程
        this.StartCoroutine(RegenerateHealth());
    }

    private IEnumerator<IYieldInstruction> RegenerateHealth()
    {
        while (true)
        {
            // 每秒恢复 1 点生命值
            yield return CoroutineHelper.WaitForSeconds(1.0);
            Health = Math.Min(Health + 1, MaxHealth);
        }
    }
}

高级用法

等待事件

using GFramework.Core.Coroutine.Instructions;

public IEnumerator<IYieldInstruction> WaitForEventExample()
{
    Console.WriteLine("等待玩家死亡事件...");

    // 等待事件触发
    var waitEvent = new WaitForEvent<PlayerDiedEvent>(eventBus);
    yield return waitEvent;

    // 获取事件数据
    var eventData = waitEvent.EventData;
    Console.WriteLine($"玩家 {eventData.PlayerId} 死亡");
}

等待事件(带超时)

public IEnumerator<IYieldInstruction> WaitForEventWithTimeout()
{
    var waitEvent = new WaitForEventWithTimeout<PlayerJoinedEvent>(
        eventBus,
        timeout: 5.0
    );

    yield return waitEvent;

    if (waitEvent.IsTimeout)
    {
        Console.WriteLine("等待超时");
    }
    else
    {
        Console.WriteLine($"玩家加入: {waitEvent.EventData.PlayerName}");
    }
}

等待 Task

public IEnumerator<IYieldInstruction> WaitForTaskExample()
{
    // 创建异步任务
    var task = LoadDataAsync();

    // 在协程中等待 Task 完成
    var waitTask = new WaitForTask(task);
    yield return waitTask;

    // 检查异常
    if (waitTask.Exception != null)
    {
        Console.WriteLine($"任务失败: {waitTask.Exception.Message}");
    }
    else
    {
        Console.WriteLine("任务完成");
    }
}

private async Task LoadDataAsync()
{
    await Task.Delay(1000);
    // 加载数据...
}

等待多个协程

public IEnumerator<IYieldInstruction> WaitForMultipleCoroutines()
{
    var coroutine1 = LoadTexture();
    var coroutine2 = LoadAudio();
    var coroutine3 = LoadModel();

    // 等待所有协程完成
    yield return new WaitForAllCoroutines(
        scheduler,
        coroutine1,
        coroutine2,
        coroutine3
    );

    Console.WriteLine("所有资源加载完成");
}

协程嵌套

public IEnumerator<IYieldInstruction> ParentCoroutine()
{
    Console.WriteLine("父协程开始");

    // 等待子协程完成
    yield return new WaitForCoroutine(scheduler, ChildCoroutine());

    Console.WriteLine("子协程完成");
}

private IEnumerator<IYieldInstruction> ChildCoroutine()
{
    yield return CoroutineHelper.WaitForSeconds(1.0);
    Console.WriteLine("子协程执行");
}

带进度的等待

public IEnumerator<IYieldInstruction> LoadingWithProgress()
{
    Console.WriteLine("开始加载...");

    yield return CoroutineHelper.WaitForProgress(
        duration: 3.0,
        onProgress: progress =>
        {
            Console.WriteLine($"加载进度: {progress * 100:F0}%");
        }
    );

    Console.WriteLine("加载完成");
}

协程标签管理

// 使用标签运行协程
var handle1 = scheduler.Run(Coroutine1(), tag: "gameplay");
var handle2 = scheduler.Run(Coroutine2(), tag: "gameplay");
var handle3 = scheduler.Run(Coroutine3(), tag: "ui");

// 停止所有带特定标签的协程
scheduler.StopAllWithTag("gameplay");

// 获取标签下的所有协程
var gameplayCoroutines = scheduler.GetCoroutinesByTag("gameplay");

延迟调用和重复调用

// 延迟 2 秒后执行
scheduler.Run(CoroutineHelper.DelayedCall(2.0, () =>
{
    Console.WriteLine("延迟执行");
}));

// 每隔 1 秒执行一次,共执行 5 次
scheduler.Run(CoroutineHelper.RepeatCall(1.0, 5, () =>
{
    Console.WriteLine("重复执行");
}));

// 无限重复,直到条件不满足
scheduler.Run(CoroutineHelper.RepeatCallWhile(1.0, () => isRunning, () =>
{
    Console.WriteLine("条件重复");
}));

与命令系统集成

using GFramework.Core.Coroutine.Extensions;

public IEnumerator<IYieldInstruction> ExecuteCommandInCoroutine()
{
    // 在协程中执行命令
    var command = new LoadSceneCommand();
    yield return command.ExecuteAsCoroutine(this);

    Console.WriteLine("场景加载完成");
}

与 CQRS 集成

public IEnumerator<IYieldInstruction> QueryInCoroutine()
{
    // 在协程中执行查询
    var query = new GetPlayerDataQuery { PlayerId = 1 };
    var waitQuery = query.SendAsCoroutine<GetPlayerDataQuery, PlayerData>(this);

    yield return waitQuery;

    var playerData = waitQuery.Result;
    Console.WriteLine($"玩家名称: {playerData.Name}");
}

最佳实践

  1. 使用扩展方法启动协程:通过架构组件的扩展方法启动协程更简洁

     this.StartCoroutine(MyCoroutine());
     scheduler.Run(MyCoroutine());
    
  2. 合理使用协程标签:为相关协程添加标签,便于批量管理

    this.StartCoroutine(BattleCoroutine(), tag: "battle");
    this.StartCoroutine(EffectCoroutine(), tag: "battle");
    
    // 战斗结束时停止所有战斗相关协程
    this.StopCoroutinesWithTag("battle");
    
  3. 避免在协程中执行耗时操作:协程在主线程执行,不要阻塞

     public IEnumerator<IYieldInstruction> BadCoroutine()
    {
        Thread.Sleep(1000); // 阻塞主线程
        yield return null;
    }
    
     public IEnumerator<IYieldInstruction> GoodCoroutine()
    {
        yield return CoroutineHelper.WaitForSeconds(1.0); // 非阻塞
    }
    
  4. 正确处理协程异常:使用 try-catch 捕获异常

    public IEnumerator<IYieldInstruction> SafeCoroutine()
    {
        var waitTask = new WaitForTask(riskyTask);
        yield return waitTask;
    
        if (waitTask.Exception != null)
        {
            // 处理异常
            Logger.Error($"任务失败: {waitTask.Exception.Message}");
        }
    }
    
  5. 及时停止不需要的协程:避免资源泄漏

    private CoroutineHandle? _healthRegenHandle;
    
    public void StartHealthRegen()
    {
        _healthRegenHandle = this.StartCoroutine(RegenerateHealth());
    }
    
    public void StopHealthRegen()
    {
        if (_healthRegenHandle.HasValue)
        {
            this.StopCoroutine(_healthRegenHandle.Value);
            _healthRegenHandle = null;
        }
    }
    
  6. 使用 WaitForEvent 时记得释放资源:避免内存泄漏

    public IEnumerator<IYieldInstruction> WaitEventExample()
    {
        using var waitEvent = new WaitForEvent<GameEvent>(eventBus);
        yield return waitEvent;
        // using 确保资源被释放
    }
    

常见问题

问题:协程什么时候执行?

解答 协程在调度器的 Update() 方法中执行。在 GFramework 中,架构会自动在每帧调用调度器的更新方法。

问题:协程是多线程的吗?

解答 不是。协程在主线程中执行,是单线程的。它们通过分帧执行来实现异步效果,不会阻塞主线程。

问题:如何在协程中等待异步方法?

解答 使用 WaitForTask 等待 Task 完成:

public IEnumerator<IYieldInstruction> WaitAsyncMethod()
{
    var task = SomeAsyncMethod();
    yield return new WaitForTask(task);
}

问题:协程可以返回值吗?

解答 协程本身不能直接返回值,但可以通过闭包或类成员变量传递结果:

private int _result;

public IEnumerator<IYieldInstruction> CoroutineWithResult()
{
    yield return CoroutineHelper.WaitForSeconds(1.0);
    _result = 42;
}

// 使用
this.StartCoroutine(CoroutineWithResult());
// 稍后访问 _result

问题:如何停止所有协程?

解答 使用调度器的 StopAll() 方法:

// 停止所有协程
scheduler.StopAll();

// 或通过扩展方法
this.StopAllCoroutines();

问题:协程中的异常会怎样?

解答 协程中未捕获的异常会触发 OnCoroutineException 事件,并停止该协程:

scheduler.OnCoroutineException += (handle, exception) =>
{
    Logger.Error($"协程异常: {exception.Message}");
};

问题WaitForSeconds 和 Delay 有什么区别?

解答 它们是相同的,WaitForSeconds 是辅助方法,内部创建 Delay 实例:

// 两者等价
yield return CoroutineHelper.WaitForSeconds(1.0);
yield return new Delay(1.0);

相关文档