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的方法 都支持异步方法