CS5L7——CSharp 5 功能和语法

本章代码关键字

1
2
async        //异步方法关键字
await //挂起方法,开启新线程的关键字,直到线程执行完毕再继续执行该方法后续逻辑

C# 5 的新增功能和语法

  1. 调用方信息特性(C#进阶——特性)
  2. 异步方法 async​ 和 await

在学习异步方法 async​ 和 await​ 之前
我们必须补充一些知识点

  1. 线程和线程池
  2. Task类

异步与同步

同步和异步主要用于修饰方法

  • 同步方法:
    当一个方法被调用时,调用者需要等待该方法执行完毕后返回才能继续执行
  • 异步方法:
    当一个方法被调用时立即返回,并获取一个线程执行该方法内部的逻辑,调用者不用等待该方法执行完毕

简单理解异步编程,我们会把一些不需要立即得到结果且耗时的逻辑设置为异步执行,
这样可以提高程序的运行效率,避免由于复杂逻辑带来的的线程阻塞

何时需要异步编程

需要处理的逻辑会严重影响主线程执行的流畅性时,我们需要使用异步编程,比如:

  1. 复杂逻辑计算时
  2. 网络下载、网络通讯
  3. 资源加载时等等

异步方法 async 和 await

async​ 和 await​ 一般需要配合 Task​ 进行使用

  • async​ 用于修饰函数、lambda​表达式、匿名函数
  • await​ 用于在函数中和 async​ 配对使用,主要作用是等待某个逻辑结束
    此时逻辑会返回函数外部继续执行,直到等待的内容执行结束后,再继续执行异步函数内部逻辑
    在一个 async​ 异步函数中可以有多个 await​ 等待关键字
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void Start()
{
TestAsync();
print("主线程逻辑执行");
}

public async void TestAsync()
{
//1
print("启动异步方法");
//2
await Task.Run(() =>
{
Thread.Sleep(5000);
});
//3
print("await完成!");
}

使用 async​ 修饰异步方法

  1. 在异步方法中使用 await关键字(不使用编译器会给出警告但不报错),否则异步方法会以同步方式执行(因为在遇到 await​ 前都会同步执行语句)
  2. 异步方法名称建议以 Async​ 结尾
  3. 异步方法的返回值只能是 void​、Task​、Task<>​,因为异步方法不能在执行方法后直接返回一个明确的返回值
  4. 异步方法中不能声明使用 ref​ 或 out​ 关键字修饰的变量

使用 await​ 等待异步内容执行完毕(一般和 Task​ 配合使用),遇到 await​ 关键字时

  1. 异步方法将被挂起

  2. 将控制权返回给调用者(即跳出该函数,回到调用者的流程里,就像同步方法 return​ 了一样,但是异步方法在这里并没有结束)

  3. await​ 修饰内容异步执行结束后,继续通过调用者线程执行后面内容

  4. await​ 也可以接收方法返回的对象,例如下面的代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    using UnityEngine;

    public class L6Test : MonoBehaviour
    {
    void Start()
    {
    LoadCubeAsync(Vector3.zero);
    }

    public async void LoadCubeAsync(Vector3 pos)
    {
    //请注意,这个await Resources.LoadAsync<GameObject>("Cube")需要第三方插件才能执行!!!
    GameObject obj = GameObject.Instantiate(await Resources.LoadAsync<GameObject>("Cube")) as GameObject;
    obj.transform.position = pos;
    }
    }

举例:

  • 复杂逻辑计算(利用 Task​ 新开线程进行计算 计算完毕后再使用 比如复杂的寻路算法)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    void Start()
    {
    CalcPathAsync(this.gameObject, Vector3.zero);
    print("主线程逻辑执行");
    }

    public async void CalcPathAsync(GameObject obj, Vector3 endPos)
    {
    print("开始处理寻路逻辑");
    int value = 10;
    await Task.Run(() =>
    {
    //模拟复杂逻辑计算
    Thread.Sleep(1000);
    value = 50;
    //这是多线程,也就是说我们不能在多线程里,访问Unity主线程场景中的对象
    });
    print("寻路完毕,处理逻辑: " + value);
    obj.transform.position = endPos;
    }
  • 计时器

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    CancellationTokenSource source;

    void Start()
    {
    Timer();
    print("主线程逻辑执行");
    }

    public async void Timer()
    {
    source = new CancellationTokenSource();
    int i = 0;
    while (!source.IsCancellationRequested)
    {
    print(i);
    await Task.Delay(1000);
    i++;
    }
    print("计时完成");
    }

    void Update()
    {
    if (Input.GetKeyDown(KeyCode.Space))
    {
    source.Cancel();
    }
    }
  • 资源加载 (Addressables​ 的资源异步加载是可以使用 async​ 和 await​ 的)

注意:Unity 中大部分异步方法是不支持异步关键字 async await的,我们只有使用协同程序进行使用

1
2
3
4
public async void LoadRes()
{
await Resources.LoadAsync("Cube"); //我们需要第三方插件才能这么做!
}

虽然官方不支持,但是存在第三方的工具(插件)可以让Unity内部的一些异步加载的方法支持异步关键字
https://github.com/svermeulen/Unity3dAsyncAwaitUtil

虽然 Unity 中的各种异步加载对异步方法支持不太好,但是当我们用到 .NET 库中提供的一些API时,可以考虑使用异步方法

  1. Web访问:HttpClient
  2. 文件使用:StreamReader、StreamWriter、JsonSerializer、XmlReader、XmlWriter​ 等等
  3. 图像处理:BitmapEncoder、BitmapDecoder​,一般 .NET 提供的 API 中 方法名后面带有 Async​ 的方法 都支持异步方法