10.CSharp各版本新功能和语法-CSharp5功能和语法-异步方法async和await
10.1 知识点
什么是同步和异步
同步和异步主要用于修饰方法。
同步方法:
当一个方法被调用时,调用者需要等待该方法执行完毕后返回才能继续执行。
异步方法:
当一个方法被调用时立即返回,并获取一个线程执行该方法内部的逻辑,调用者不用等待该方法执行完毕。
简单理解异步编程:
我们会把一些不需要立即得到结果且耗时的逻辑设置为异步执行,这样可以提高程序的运行效率,避免由于复杂逻辑带来的线程阻塞。
什么时候需要异步编程
需要处理的逻辑会严重影响主线程执行的流畅性时,我们需要使用异步编程。比如:
- 复杂逻辑计算时
- 网络下载、网络通讯
- 资源加载时
等等
异步方法async和await
async和await一般需要配合Task进行使用。
- async用于修饰函数、lambda表达式、匿名函数。
- await用于在函数中和async配对使用,主要作用是等待某个逻辑结束。此时逻辑会返回函数外部继续执行,直到等待的内容执行结束后,再继续执行异步函数内部逻辑。在一个async异步函数中可以有多个await等待关键字。
- 简单来说就是async修饰函数,await修饰Task,遇到await修饰Task回去执行主线程逻辑,等Task逻辑执行完再执行async修饰函数里的逻辑。
使用async修饰异步方法的注意事项
- 在异步方法中使用await关键字(不使用编译器会给出警告但不报错),否则异步方法会以同步方式执行。
- 异步方法名称建议以Async结尾。
- 异步方法的返回值只能是void、Task、Task<>。
- 异步方法中不能声明使用ref或out关键字修饰的变量。
使用await等待异步内容执行完毕的说明
遇到await关键字时:
- 异步方法将被挂起。
- 将控制权返回给调用者。
- 当await修饰内容异步执行结束后,继续通过调用者线程执行后面内容。
举例说明
普通异步方法
// 普通异步方法
public async void TestAsync()
{
print("进入异步方法");
await Task.Run(() =>
{
print("异步方法Task内");
Thread.Sleep(5000); // 在异步任务中,线程暂停5秒钟(模拟耗时操作)
});
print("异步方法后面的逻辑");//这句会在五秒后才打印
}
TestAsync();
print("主线程逻辑执行");
复杂逻辑计算
// 复杂逻辑计算(利用Task新开线程进行计算 计算完毕后再使用 比如复杂的寻路算法)
public async void CalcPathAsync(GameObject obj, Vector3 endPos)
{
print("开始处理寻路逻辑");
int value = 10;
await Task.Run(() =>
{
print("处理复杂逻辑计算");
Thread.Sleep(1000); // 处理复杂逻辑计算,模拟耗时操作
value = 50;
// 不能在多线程里访问 Unity 主线程场景中的对象
// print(obj.transform.position);
});
print("寻路计算完毕 处理逻辑" + value);//这句会在一秒后才打印
obj.transform.position = Vector3.zero;
}
CalcPathAsync(this.gameObject, Vector3.zero);
计时器
// 计时器
CancellationTokenSource cancellationTokenSource;
void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
cancellationTokenSource.Cancel(); // 请求取消操作
}
public async void Timer()
{
UnityWebRequest unityWebRequest = UnityWebRequest.Get(""); // 创建一个 UnityWebRequest 对象,用于发送网络请求
cancellationTokenSource = new CancellationTokenSource(); // 创建一个 CancellationTokenSource 对象,用于取消任务
int i = 0; // 初始化计数器 i
while (!cancellationTokenSource.IsCancellationRequested) // 当 cancellationTokenSource 没有请求取消时,执行循环
{
print(i); // 打印当前计数器值
await Task.Delay(1000); // 延迟等待 1000 毫秒(1秒)
++i; // 计数器增加1
}
}
Timer();
print("主线程逻辑执行");
资源加载(Addressables的资源异步加载是可以使用async和await的)
Unity使用异步时的注意事项
- Unity中大部分异步方法不支持异步关键字async和await,需要使用协同程序进行使用。
- 虽然官方不支持,但是存在第三方工具(插件)可以让Unity内部的一些异步加载的方法支持异步关键字,如Unity3dAsyncAwaitUtil,网址是:https://github.com/svermeulen/Unity3dAsyncAwaitUtil。
- 在使用到.Net库中提供的一些API时,可以考虑使用异步方法。例如:Web访问(HttpClient)、文件使用(StreamReader、StreamWriter、JsonSerializer、XmlReader、XmlWriter等等)、图像处理(BitmapEncoder、BitmapDecoder)等。一般.Net提供的API中方法名后面带有Async的方法都支持异步方法。
总结
异步编程async和await是一个比较重要的功能,我们可以利用它配合Task进行异步编程。
虽然Unity自带的一些异步加载原本是不支持异步方法关键字的,但是可以利用别人写好的第三方工具让它们支持。大家可以根据自己的需求选择性使用。
10.2 知识点代码
using System.Collections;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.SceneManagement;
public class Lesson10_CSharp各版本新功能和语法_CSharp5功能和语法_异步方法async和await : MonoBehaviour
{
void Start()
{
#region 知识点一 什么是同步和异步
//同步和异步主要用于修饰方法
//同步方法:
//当一个方法被调用时,调用者需要等待该方法执行完毕后返回才能继续执行
//异步方法:
//当一个方法被调用时立即返回,并获取一个线程执行该方法内部的逻辑,调用者不用等待该方法执行完毕
//简单理解异步编程
//我们会把一些不需要立即得到结果且耗时的逻辑设置为异步执行,这样可以提高程序的运行效率
//避免由于复杂逻辑带来的的线程阻塞
#endregion
#region 知识点二 什么时候需要异步编程
//需要处理的逻辑会严重影响主线程执行的流畅性时
//我们需要使用异步编程
//比如:
//1.复杂逻辑计算时
//2.网络下载、网络通讯
//3.资源加载时
//等等
#endregion
#region 知识点三 异步方法async和await
//async和await一般需要配合Task进行使用
//async用于修饰函数、lambda表达式、匿名函数
//await用于在函数中和async配对使用,主要作用是等待某个逻辑结束
//此时逻辑会返回函数外部继续执行,直到等待的内容执行结束后,再继续执行异步函数内部逻辑
//在一个async异步函数中可以有多个await等待关键字
//使用async修饰异步方法
//1.在异步方法中使用await关键字(不使用编译器会给出警告但不报错),否则异步方法会以同步方式执行
//2.异步方法名称建议以Async结尾
//3.异步方法的返回值只能是void、Task、Task<>
//4.异步方法中不能声明使用ref或out关键字修饰的变量
//使用await等待异步内容执行完毕(一般和Task配合使用)
//遇到await关键字时
//1.异步方法将被挂起
//2.将控制权返回给调用者
//3.当await修饰内容异步执行结束后,继续通过调用者线程执行后面内容
//举例说明
//1.普通异步方法
TestAsync();
print("主线程逻辑执行");
//2.复杂逻辑计算(利用Task新开线程进行计算 计算完毕后再使用 比如复杂的寻路算法)
CalcPathAsync(this.gameObject, Vector3.zero);
//3.计时器
Timer();
print("主线程逻辑执行");
//4.资源加载(Addressables的资源异步加载是可以使用async和await的)
//注意:Unity中大部分异步方法是不支持异步关键字async和await的,我们只有使用协同程序进行使用
//虽然官方 不支持 但是 存在第三方的工具(插件)可以让Unity内部的一些异步加载的方法 支持 异步关键字
//https://github.com/svermeulen/Unity3dAsyncAwaitUtil
//虽然Unity中的各种异步加载对异步方法支持不太好
//但是当我们用到.Net 库中提供的一些API时,可以考虑使用异步方法
//1.Web访问:HttpClient
//2.文件使用:StreamReader、StreamWriter、JsonSerializer、XmlReader、XmlWriter等等
//3.图像处理:BitmapEncoder、BitmapDecoder
//一般.Net 提供的API中 方法名后面带有 Async的方法 都支持异步方法
#endregion
#region 总结
//异步编程async和await是一个比较重要的功能
//我们可以利用它配合Task进行异步编程
//虽然Unity自带的一些异步加载原本是不支持 异步方法关键字的
//但是可以利用别人写好的第三方工具 让他们支持 大家可以根据自己的需求 选择性使用
#endregion
}
#region 知识点三 异步方法async和await
public async void TestAsync()
{
//1 打印"进入异步方法"
print("进入异步方法");
//2 使用Task.Run创建一个异步任务,并等待其完成(await关键字)
await Task.Run(() =>
{
print("异步方法Task内");
Thread.Sleep(5000); // 在异步任务中,线程暂停5秒钟(模拟耗时操作)
});
//3 打印"异步方法后面的逻辑"
print("异步方法后面的逻辑");
}
public async void CalcPathAsync(GameObject obj, Vector3 endPos)
{
print("开始处理寻路逻辑");
int value = 10;
await Task.Run(() =>
{
print("处理复杂逻辑计算");
//处理复杂逻辑计算 我这是通过 休眠来模拟 计算的复杂性
Thread.Sleep(1000);
value = 50;
//是多线程 意味着我们不能在 多线程里 去访问 Unity主线程场景中的对象
//这样写会报错
//print(obj.transform.position);
});
print("寻路计算完毕 处理逻辑" + value);
obj.transform.position = Vector3.zero;
}
CancellationTokenSource cancellationTokenSource;
void Update()
{
// 检测是否按下了空格键
if (Input.GetKeyDown(KeyCode.Space))
// 请求取消操作
cancellationTokenSource.Cancel();
}
public async void Timer()
{
// 创建一个 UnityWebRequest 对象,用于发送网络请求
UnityWebRequest unityWebRequest = UnityWebRequest.Get("");
// 创建一个 CancellationTokenSource 对象,用于取消任务
cancellationTokenSource = new CancellationTokenSource();
// 初始化计数器 i
int i = 0;
// 当 cancellationTokenSource 没有请求取消时,执行循环
while (!cancellationTokenSource.IsCancellationRequested)
{
// 打印当前计数器值
print(i);
// 延迟等待 1000 毫秒(1秒)
await Task.Delay(1000);
// 计数器增加1
++i;
}
}
#endregion
}
10.3 练习题
请导入第三方UnityAsyncAwaitUtil工具包到工程中,然后利用异步关键字async和await配合Resources中的异步加载方法LoadAsync加载并实例化出一个立方体预设体
方法一
public async void CreateCubeAsync1()
{
print("进入直接异步创建Cube方法");
ResourceRequest resourceRequest = Resources.LoadAsync<GameObject>("Cube");
await resourceRequest;
Instantiate(resourceRequest.asset);
print("退出直接异步创建Cube方法");
}
CreateCubeAsync1();
方法二
public async Task CreateCubeAsync2()
{
print("进入直接异步创建Cube方法2");
ResourceRequest resourceRequest = Resources.LoadAsync<GameObject>("Cube");
// 使用 Resources.LoadAsync 加载名为 "Cube" 的预制体资源,并创建一个 ResourceRequest 对象用于跟踪加载过程
await resourceRequest;
// 使用 await 关键字等待 ResourceRequest 完成,即加载完成
Instantiate(resourceRequest.asset);
// 在场景中实例化加载的 Cube 预制体资源,resourceRequest.asset 是加载完成的预制体资源
print("退出直接异步创建Cube方法2");
}
public async void IndirectCreateCubeAsync()
{
print("进入间接异步创建Cube方法");
await CreateCubeAsync2();
print("退出间接异步创建Cube方法");
}
IndirectCreateCubeAsync();
方法三
10.4 练习题代码
using System.Collections;
using System.Collections.Generic;
using System.Threading.Tasks;
using UnityEngine;
public class Lesson10_练习题 : MonoBehaviour
{
void Start()
{
#region 练习题一 请导入上节课资料区中的 第三方UnityAsyncAwaitUtil工具包到工程中,然后利用异步关键字async和await配合Resources中的异步加载方法LoadAsync加载并实例化出一个立方体预设体
CreateCubeAsync1();
IndirectCreateCubeAsync();
print("主线程执行");
#endregion
}
public async void CreateCubeAsync1()
{
print("进入直接异步创建Cube方法");
ResourceRequest resourceRequest = Resources.LoadAsync<GameObject>("Cube");
await resourceRequest;
Instantiate(resourceRequest.asset);
print("退出直接异步创建Cube方法");
}
public async Task CreateCubeAsync2()
{
print("进入直接异步创建Cube方法2"); // 打印一条消息,指示进入异步创建 Cube 的方法
ResourceRequest resourceRequest = Resources.LoadAsync<GameObject>("Cube");
// 使用 Resources.LoadAsync 加载名为 "Cube" 的预制体资源,并创建一个 ResourceRequest 对象用于跟踪加载过程
await resourceRequest;
// 使用 await 关键字等待 ResourceRequest 完成,即加载完成
Instantiate(resourceRequest.asset);
// 在场景中实例化加载的 Cube 预制体资源,resourceRequest.asset 是加载完成的预制体资源
print("退出直接异步创建Cube方法2"); // 打印一条消息,指示退出异步创建 Cube 的方法
}
public async void IndirectCreateCubeAsync()
{
print("进入间接异步创建Cube方法");
await CreateCubeAsync2();
print("退出间接异步创建Cube方法");
}
}
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com