工作之后在很多项目中遇到了async相关的编程知识,这一块笔者了解甚少,属于是实践之后又回来学习理论,有一些小的心得分享给大家同时也巩固自身的学习
异步
对于如下一段程序
{
A();
B();
}
同步方式:运行A -> 等待运行A -> 等待A运行完了运行B
异步方式:运行A -> 等待运行A -> 在等待的过程中去运行CDEFG... ->A运行完了运行B额
一、async 和 await
异步方法使用 async修饰。其返回值类型为Task或者void,其中Task表示异步操作。
等待一个异步操作的完成使用await ,await只能在异步方法中使用。
await A; =》如果A还没有完成,那么就暂停程序的顺序执行,并将控制权返回给调用者,调用者去干别的事,直到异步操作完成。
当异步操作完成之后,await会自动恢复后续代码执行。
二、Task
Task代表一个异步操作,相当于是一个将要完成任务的句柄的存在。
为什么异步方法返回Task
同步方法可以直接返回结果,因为计算已经完成,而异步方法返回时,计算还没完成,没法直接给Int,此时此刻需要一个容器来表示这个操作还在进行中,将来存放计算结果,将来存放异常信息,提供等待,回调,取消等能力。而这个容器就是Task
c#
同步方法
public int GetResult(){
Thread.Sleep(1000); //等待一秒
return 42; //一秒后返回
异步方法
public Task<int> GetResultAsync(){
await Task.Delay(1000) //不阻塞线程,先立马返回给你一个Task,这1秒你先去干别的活
return 42; //1秒后结果就放到task中
}
Task结构
概念上,task内部大概是这样
c#
class Task<int>{
int _result;
bool _isCompleted;
Exception _error;
public void Wait(){}
public int Result{get{...}}
}
三、并发调用
并发调用就是等待多个异步任务它们全部完成的方法。
主要是靠 Task.WhenAll 和 Task.WhenAny这俩兄弟来完成。
- Task.WhenAll => 是C#中并发执行多个任务并等待它们全部完成的方法。
- Task.WhenAny => 是C#中并发执行多个任务并等待其中任何一个任务完成的方法。
它们都接受一个Task数组作为参数。
四、取消令牌Cancellation Token
在异步操作中,可能会遇到需要取消任务的情况,C#提供了CancellationToken来支持取消任务的机制,通过传递取消令牌,可以在异步方法中处理取消请求,提高程序的灵活性和响应性。
而Cancellation Token 是一种协作机制,操作自己决定何时检查,如何退出。
C#
// 1. 创建 CancellationTokenSource
using var cts = new CancellationTokenSource();
// 2. 获取 Token 传给要支持取消的操作
CancellationToken token = cts.Token;
// 3. 在异步/后台操作中检查取消
Task.Run(() => DoWork(token), token); // 注意同时传入 token 给 Task.Run
// 4. 在适当的时候发出取消请求
cts.Cancel();
void DoWork(CancellationToken token)
{
for (int i = 0; i < 100; i++)
{
// 方式一:检查 IsCancellationRequested(推荐非频繁操作)
if (token.IsCancellationRequested)
{
// 清理资源后退出
Console.WriteLine("检测到取消,退出");
return;
}
// 方式二:调用 ThrowIfCancellationRequested(推荐用于需要抛出异常终止的地方)
token.ThrowIfCancellationRequested();
// 执行实际工作...
Thread.Sleep(100);
}
}
public async Task DownloadAsync(string url, CancellationToken token)
{
var client = new HttpClient();
// 支持取消的异步方法可以传入 token
var response = await client.GetAsync(url, token);
var data = await response.Content.ReadAsStringAsync();
// 也可以在自己的循环中检查
await foreach (var item in ProcessStreamAsync(token).WithCancellation(token))
{
// ...
}
}
令牌底层结构
c#
// 1. CancellationTokenSource - 信号的"发射器"
public class CancellationTokenSource : IDisposable
{
private CancellationTokenCallback _callbacks; // 回调链表
private int _state; // 0: None, 1: Cancelled, 2: Disposed
public void Cancel() // 发出取消信号
public CancellationToken Token { get; } // 获取"接收器"
public static CancellationTokenSource CreateLinkedTokenSource(...)
}
// 2. CancellationToken - 信号的"接收器"(结构体,零开销)
public struct CancellationToken
{
private CancellationTokenSource _source; // 引用源
public bool IsCancellationRequested { get; } // 检查是否被取消
public void ThrowIfCancellationRequested() // 取消时抛异常
public CancellationTokenRegistration Register(Action callback) // 注册回调
}
// 3. OperationCanceledException - 约定的"终止信号"
public class OperationCanceledException : Exception
{
public CancellationToken? CancellationToken { get; }
}
五、UniTask-Unity中的异步
UniTask是Unity版本的异步编程,比原生Task更快,更适合Unity中的异步操作。
没有UniTask的时候,我们往往使用协助程。
而UniTask帮助解决没有返回值,不能try-catch,处理GC问题。