Unity3D中的C#协程(概念、使用方法、底层原理)

        Unity3D 中的协程是针对 Unity3D 框架和 C# 编程语言定制的,具有便捷的使用方式和良好的效率。其他语言Python、Lua等也支持协程,但是底层实现的细节可能不同。在 Unity3D 引擎中,协程被 Unity3D 引擎的主循环所驱动


协程概念

        协程(Coroutine)是一种编程概念,是一种轻量级的用户空间线程在 Unity3D 中被广泛用于处理异步操作、延迟执行和分帧处理等任务。协程在 Unity3D 中可以让程序员用类似于同步编码的样式来实现异步操作,从而使代码更易于阅读和理解。行非抢占式的任务切换。这些代码块也称为协程。


协程作用

        协程可以通过将一个复杂的任务分解成多个步骤来模拟异步处理,在每个帧之间“暂停”并将控制权返回给 Unity3D 主线程。这使得协程在特定的时间或条件的满足后恢复执行。协程通过让程序流程主动挂起yield和恢复,从而使你能够编写看似线性的代码,同时处理非阻塞性操作,如等待时间、访问网络资源等。协程的主要优点是可以在对等地位的特定代码块之间进协程在 Unity3D 中常用来处理任务的延迟(场景加载、异步加载AssetBundle等资源)、I/O操作、网络通信,因为它们可以防止阻塞主线程,从而确保游戏运行流畅。


协程的常用写法

// 下一帧再执行后续代码
yield return null;
yield return 0;
yield return 6;		// 任意数字

// 直接结束该协程的后续操作
yield break;

// 等待异步操作结束后再执行后续代码
yield return asyncOperation;

// 等待某个协程执行完毕后再执行后续代码
yield return StartCoroutine(某个协程函数);

// 等待某个函数完成后再执行后续代码
yield return Function();

// 等待帧结束(所有的摄像机和GUI被渲染完成后,在该帧显示在屏幕之前执行)
yield return new WaitForEndOfFrame();

// 等待 所有的Update函数完成的那一帧之后 一段指定的时间延迟(秒)之后继续执行
yield return new WaitForSeconds(0.3f);

// 等待下一次FixedUpdate开始时再执行后续代码
yield return new WaitForFiexdUpdate();

// 将协同执行直到 当前输入的参数(或者委托)为true的时候
yield return new WaitUntil();

// 将协同执行直到 当前输入的参数(或者委托)为false的时候
yield return new WaitWhile();
// for 循环,每帧
for(int i = 0; i < 10; i++)
{
    Debug.Log(i);
    yield return null;
}

// 下载网络资源
UnityWebRequest req = UnityWebRequest.Get("http://127.0.0.1:6080/game_map");
yield return req.Send();
Debug.Log("download success" + req.downloadedBytes);

// 异步加载AssetBundle
AssetBundle ab = AssetBundle.LoadFromMemory(req.downloadHandler.data);
AssetBundleRequest abReq = ab.LoadAssetAsync<GameObject>(Assets/Resources/GameObject.prefab);
yield return abReq;
GameObject obj = abReq.asset as GameObject;
ab.Unload(false);

协程的底层 IEnumerator 与 yield

        协程的调度完全由开发者控制,而不是由系统底层来管理

        在Unity底层,协程由MonoBehaviourStartCoroutine方法创建、启动和调度。当一个协程方法被调用时,Unity会将协程与一个迭代器一起添加到待处理队列。然后,Unity将在每一帧的Update方法中处理队列中的协程,直到协程完成或被停止。

        协程是基于迭代器IEnumerator和C#的yield关键字实现的。对于IEnumerator迭代器我的理解是,他是一个函数对象的一个容器。协程函数可以通过使用yield return关键字将任务分解成多个片段,保存上下文。简单来说就是,协程函数通过yield关键字可以帮我们抽出函数代码块(前一个yield与当前yield之间的代码块)生成一个函数放到IEnumerator容器,最后协程函数返回这个迭代器。协程就是将IEnumerator里的函数,每隔一帧依次执行一个,这通过IEnumerator接口的MoveNext()Current属性来实现,MoveNext()用于处理协程的下一个步骤,Current则表示当前的执行结果。

        协程就是将IEnumerator里引用的代码段,每隔一帧执行一次

// 模拟StartCoroutine
public IEnumerator nowEnum;
public void My_StartCoroutine(IEnumerator e)
{
    onwEnum = e;
}

void LateUpdate()
{
    if(nowEnum != null)
    {
        if(!nowEnum.MoveNext())
        {
            nowEnum = null;
        }
    }
}

        Unity3D 中的协程并不像传统的线程或者其他多任务实现那样涉及到堆栈指针、指令指针等底层上下文切换。相反,它依赖于 C# 编译器生成的迭代器和状态机,提供了一种轻量级的、基于语言特性实现的异步编程方式。但是值得注意的是,协程在 Unity3D 中并不是一个真正的多线程特性,它只是提供了一种编程模式,看起来具有异步的效果。在线程本身中(在 Unity3D 的主线程中),协程会按照指定的顺序、同步地执行。


Unity3D 中的协程与线程相比

虽然 Unity3D 中的协程和线程都是用来处理异步任务的,但它们之间有一些明显的区别:

  1. 实现方式:
    1. 协程是基于 C# 的 IEnumerator 接口实现的,可以视为一种伪异步编程方式;
    2. 线程是基于系统底层的多线程模型实现,能够运行在操作系统提供的不同的独立执行单元中,实现真正的多任务并行执行。
  2. 管理方式
    • 协程是用户级的概念,它们的创建、管理和调度主要是在用户程序中完成的。
    • 线程主要由操作系统进行管理和调度。当线程切换发生时,保存和恢复线程执行的上下文和状态是由操作系统内核来完成的。
  3. 上下文切换消耗:
    1. 协程的上下文切换消耗较小,因为它仅依赖 C# 编译器生成的状态机;
    2. 线程间的上下文切换消耗较大(需要保存和恢复堆栈指针、指令指针等),在多线程环境下可能会产生性能问题。
  4. CPU 利用:
    1. 协程运行在 Unity3D 的主线程上,任务执行是有序且在同一个线程中的,这意味着协程的任务不能充分利用多核 CPU 的优势;
    2. 线程是独立的执行单元,可在多核 CPU 上同时运行,更好地利用计算资源。
  5. 并发控制:
    1. 协程在同一时间只能运行一个任务,降低了并发问题的发生概率;
    2. 线程并行执行,需要考虑死锁、竞态条件等并发控制问题,可能需要使用互斥锁、信号量等机制进行同步和通信。
  6. 适用场景:
    1. 协程适用于需要分步执行、非密集计算类型的任务,例如延时、逐帧更新、网络请求等;
    2. 线程适用于较复杂、计算密集型、需要并行执行的任务,如图形渲染、大数据处理等。

        

        总之,协程和线程在 Unity3D 中有各自的优缺点,适用于不同的应用场景。根据项目需求,选择适当的异步策略对于性能和可维护性都至关重要。在 Unity3D 的协程方案中如果需要处理密集型任务可以结合 ThreadPool 或者 C# Task 类库,并在需要时使用线程安全的数据结构以确保正确并发操作。

  • 3
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值