Bootstrap

C# & Unity 同步/异步编程和多线程什么关系?async/await和coroutine又是什么?

目录 不用模板生成的目录怎么这么丑啊

1.同步?异步?多线程?

2.async/await和coroutine?

证明

单线程中的同步/异步

多线程中的同步异步

1.同步?异步?多线程?

首先,你需要知道什么是同步编程,什么是异步编程

同步编程:所有代码按顺序执行,直到当前任务完成后才开始下一个任务

异步编程:代码可以在等待某些任务完成时继续执行其他任务

其次,你需要知道什么是多线程

多线程:是一种并发编程技术,允许一个程序同时执行多个线程

好,既然你能明白概念,说明你非常聪明,可以直接上第一个结论了

当然不懂也没关系,先有个印象我会说明为什么

2.async/await和coroutine?

我直接指名加油站,看懂了就行,二者都是异步处理的解决方法

async/await:async/await 用法,看这一篇就够了_async await用法-CSDN博客

coroutine:unity保姆级教程之协同程序_unity 协同程序实现原理-CSDN博客

请看下图结论 

证明

单线程中的同步/异步

所有代码按顺序执行,直到当前任务完成后才开始下一个任务

举一个我家老太太都会写的例子

public class Single : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        Func1();
        Func2();
    }

    void Func1()
        {
        Debug.Log("这是函数1 所需要执行的话");
    }
    void Func2() {
        Debug.Log("这是函数2 所需要执行的话");
    }
}

结果,注意时间

就相当于如此

代码可以在等待某些任务完成时继续执行其他任务

用协程函数

public class Single : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        StartCoroutine(Wait());
    }
    IEnumerator Wait()
        {
        Func1();
        yield return new WaitForSeconds(1.0f);
        Func2 ();
    }

    void Func1()
        {
        Debug.Log("这是函数1 所需要执行的话");
    }
    void Func2() {
        Debug.Log("这是函数2 所需要执行的话");
    }
}

注意时间 

用async/await

public class Single : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        Wait();
    }
    async void Wait() {
        Func1();
        await Task.Delay(1000); // 等待1秒
        Func2();
    }

结果当然是毫不意外

 二者用图解 皆是如此

牢记这句话:代码可以在等待某些任务完成时继续执行其他任务

我可以在这1s打个胶还是什么都行,是不是非常严谨

多线程中的同步异步

多线程会遇到一个问题,就是新开的线程是独立的,并不会受到启动顺序的影响

比如:

  void Start()
  {
      Thread t1 = new Thread(Func1);
      Thread t2 =new Thread(Func2);
      t1.Start();
      t2.Start();
  }

那么就会出现这种情况:

即:有时候会是func1先执行,有时候会使func2先执行

那么要是使其实现同步的话,就需要加一个小小的类ManualResetEvent 类 (System.Threading) | Microsoft Learn

这样就可以实现,所有代码按顺序执行,直到当前任务完成后才开始下一个任务

public class Single : MonoBehaviour
{
    ManualResetEvent resetEvent = new ManualResetEvent(false);
    // Start is called before the first frame update
    void Start()
    {
        Thread t1 = new Thread(Func1);
        Thread t2 =new Thread(Func2);
        t1.Start();
        t2.Start();
    }

    void Func1()
        {
        Debug.Log("这是函数1 所需要执行的话");
        resetEvent.Set();
    }
    void Func2() {
        resetEvent.WaitOne();
        Debug.Log("这是函数2 所需要执行的话"); 
    }
}

 另外你需要注意的一个点就是在unity中,其有自己的一个主线程用于执行生命周期函数,图解如下:

代码可以在等待某些任务完成时继续执行其他任务

public class Single : MonoBehaviour {
    // TaskCompletionSource 用于在异步任务之间传递信号
    private TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>();

    async void Start() {
        Task t1= Func1();
        Debug.Log("看看这句话是在哪执行的1");
        Task t2 = Func2();
        Debug.Log("看看这句话是在哪执行的2");
        // 等待两个任务都完成
        await Task.WhenAll(t1, t2);
       
    }

    async Task Func1() {
        Debug.Log("这是函数1 所需要执行的话");

        // 设置 TaskCompletionSource 的结果,通知 Func2 可以继续执行
        tcs.SetResult(true);
    }

    async Task Func2() {
        // 等待 Func1 完成
        await tcs.Task;
        // 延迟一秒
        await Task.Delay(1000);
        Debug.Log("这是函数2 所需要执行的话");
    }
}

这我就不得不多说两句了,在这段代码中unity有自己的主线程,而t1和t2是副线程

当调用t1和t2时,这两个线程会在后台并行执行,而主线程会继续执行后续的代码。主线程和工作线程之间是并行运行的,互不阻塞

协程同理

public class Single : MonoBehaviour {
    Thread t1;
    Thread t2;
   void Start() {
        t1 = new Thread(Func1);
        t2 = new Thread(Func2);
        Debug.Log("看看这句话输出在哪里1");
        StartCoroutine(wait());
        Debug.Log("看看这句话输出在哪里2");
    }

    IEnumerator wait()
        { 
    t1.Start();
    yield return new WaitForSeconds(1.0f);
     t2.Start ();
    }
    // 异步方法 Func1
    void Func1() {
        // 输出调试信息
        Debug.Log("这是函数1 所需要执行的话");

    }

    // 异步方法 Func2
    void Func2() {

        Debug.Log("这是函数2 所需要执行的话");
    }
}

输出结果很意思,这明显不是按照顺序来的

更加证明了这是异步的,记住:代码可以在等待某些任务完成时继续执行其他任务

;