协程和异步的区别

协程和异步的区别

协程和异步的区别

协程 (Coroutine)

  • 协程是一种在Unity中常见的用来处理异步任务的方法,它允许在多帧之间分割代码执行。

  • 协程由Unity的MonoBehaviour类提供的StartCoroutine方法启动。

  • 协程通过yield语句来暂停执行,直到满足特定条件再继续执行。

  • 协程的执行仍然是在主线程上,它并不会创建新的线程。

  • 例子:

    1
    2
    3
    4
    5
    6
    IEnumerator ExampleCoroutine()
    {
    Debug.Log("Coroutine started");
    yield return new WaitForSeconds(2);
    Debug.Log("Coroutine resumed after 2 seconds");
    }

异步 (Asynchronous)

  • 异步操作允许任务在后台执行,不会阻塞主线程。异步任务通常使用asyncawait关键字。

  • 异步操作可以使用多线程或事件循环来处理长时间运行的任务而不阻塞用户界面。

  • 异步编程可以在Unity中使用C#的Task并行库。

  • 例子:

    1
    2
    3
    4
    5
    6
    async Task ExampleAsync()
    {
    Debug.Log("Async task started");
    await Task.Delay(2000); // Simulates a delay of 2 seconds
    Debug.Log("Async task resumed after 2 seconds");
    }

用问题形式加深理解

问题1

1
2
Addressable.LoadSceneAsync(sceneReference, LoadSceneMode.Additive);
Next();

Addressable.LoadSceneAsync()这是一个异步操作, 没有加载完firstLoadScene场景之前, 请问会执行Next()函数吗?

Addressable.LoadSceneAsync 是一个异步方法,用于异步加载场景。调用这个方法时,Unity并不会等待场景加载完成就继续执行接下来的代码。这意味着在Addressable.LoadSceneAsync调用后立即执行的Next();函数不会等待场景加载完成。

异步方法的设计理念就是允许程序在等待耗时操作(如场景加载)的同时继续执行其他代码,避免了阻塞主线程,从而保持应用的响应性。因此,在Addressable.LoadSceneAsync调用之后,Next()函数会被立即调用,无论场景是否已经加载完成。

如果需要确保某些代码在场景加载完成后才执行,可以使用await关键字或者添加一个回调或者事件监听来处理加载完成的情况。例如,可以使用AsyncOperation对象的completed事件或者在Addressable.LoadSceneAsync调用中获取的Task来实现这一点。

这里有一个使用async/await的例子:

1
2
3
4
5
6
public async void LoadSceneAndThen()
{
var operation = Addressable.LoadSceneAsync(firstLoadScene.sceneReference, LoadSceneMode.Additive);
await operation.Task;
Next();
}

在这个例子中,Next()将会在场景加载完成之后才被调用。这是通过await operation.Task等待加载操作完成实现的。注意,包含await关键字的方法也必须声明为async

问题2

1
2
StartCoroutine(A());
B();

这是一个协程操作, 请问没有完成A()时, 会执行B()函数吗?

在Unity中使用StartCoroutine启动一个协程时,该协程会在后台异步执行,而不会阻塞主线程。

B()函数将在StartCoroutine(A());调用之后立即执行,不会等待协程A()完成。这是因为StartCoroutine并不等待协程执行完毕,而是立即返回控制权给调用者,让其可以继续执行后续的代码。

协程A()将在下一个更新循环开始执行,并且在其每次迭代(通常是在每帧)之间挂起,直到下一次迭代再次开始。同时,主线程上的所有其他代码(包括B()函数)都会继续正常运行。

如果需要在协程A()完成后执行B(),可以考虑在协程A()内部使用yield return语句,当协程结束时,通过某种方式(如委托、事件或公共方法)通知外部代码,然后在外部代码中安排相应的操作。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 在协程A中
void A()
{
// 协程逻辑...
yield return null; // 等待到下一帧
OnCoroutineAComplete(); // 假设OnCoroutineAComplete是一个通知外部的方法
}

// 外部代码中
private void OnCoroutineAComplete()
{
B();
}

这样设计可以确保B()只在协程A()完成后执行,同时保持代码的非阻塞性质,不会影响主线程的运行。

问题3

协程是开辟新线程还是使用主线程?

协程在Unity中是使用主线程执行的,并不会开辟新线程。协程通过在每一帧的更新循环中进行分时调度来实现异步行为。通过使用yield语句,协程可以暂停执行并在将来的某个时间点继续执行,但这一切都是在主线程上完成的。

具体来说,协程的执行流程如下:

  1. 当调用StartCoroutine方法时,协程开始运行。
  2. 在协程内部遇到yield语句时,协程暂停执行并返回控制权给主线程。
  3. 在后续的帧更新中,Unity会检查所有暂停的协程并根据条件恢复执行。

因此,协程不会阻塞主线程,它们只是利用了Unity的更新循环在多个帧之间分配任务,从而实现了非阻塞的行为。

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
using UnityEngine;
using System.Collections;

public class CoroutineExample : MonoBehaviour
{
void Start()
{
StartCoroutine(MyCoroutine());
// 这里的代码会立即执行,不会等待MyCoroutine结束
Debug.Log("Start method continues");
}

IEnumerator MyCoroutine()
{
Debug.Log("Coroutine starts");
yield return new WaitForSeconds(2); // 暂停协程2秒
Debug.Log("Coroutine resumes after 2 seconds");
}
}

在这个例子中,Start()方法会立即继续执行并输出"Start method continues",而协程MyCoroutine会在2秒后恢复并输出"Coroutine resumes after 2 seconds"。整个过程中,所有代码都是在主线程上运行的。

总结

  1. 启动协程StartCoroutine(MyCoroutine());, 和异步操作, 都不会让程序阻塞, 程序会继续执行下面的语句
  2. 协程内部的yield语句会阻塞程序, 下面的语句不会执行, 会等待该语句执行完毕才能执行
  3. 协程没有开辟新线程, 协程依赖于Unity的更新循环, 协程由Unity的MonoBehaviour类提供的StartCoroutine()方法启动
  4. 二者都可以处理异步任务

协程和异步的区别
http://example.com/协程和异步的区别/
作者
李小基
许可协议