async异步方案
async 是c#后面出的异步解决方案,不同于 yield 的生产者消费者模式
async 是完整的异步方案,包括异常处理,返回值处理等
async 的实现原理是编译器处理 async ,封装成状态机,配合 await 操作符注入回调
async/await的标准写法
// 一般要传入 cancelToken
// 正如事件监听器一样,异步开始时注册监听器,取消时要反注册监听器
// 而async取消操作是由 CancellationToken 完成
public async Task TestAsync(CancellationToken cancelToken)
{
try
{
}
catch (Exception ex) when (!(ex is OperationCanceledException))
{
// OperationCanceledException 是取消操作抛出的异常,一般不处理
// 只需要在 finally 中清理资源即可,最外层也会忽略该异常,不会报错
}
finally
{
// 当异步正常完成或取消后,在这里清理资源
}
// 如果整个函数中没有任何 await 执行,则编译器会发出警告,可以加入下面这句来避免警告
// 有返回值的可以用 await Task.FromResult(ret);
await Task.CompletedTask;
}
// 调用方标准写法
// 最外层由同步函数转异步函数,只能使用 async void
CancellationTokenSource m_cancelTokenSource;
public async void Execute()
{
// 创建一个可取消的源
m_cancelTokenSource = new CancellationTokenSource();
await TestAsync(cancelTokenSource.Token);
}
// 取消异步操作
public void Cancel()
{
m_cancelTokenSource.Cancel();
}
取消任务
-
所有任务都接受1个 CancellationToken 参数用来取消,你的async或自定义任务设计时也要这样
1个token可以被多个await使用var cts = new CancellationTokenSource(); cancelButton.onClick.AddListener(() => { cts.Cancel(); }); await UnityWebRequest.Get("http://google.co.jp").SendWebRequest().WithCancellation(cts.Token); await UniTask.DelayFrame(1000, cancellationToken: cts.Token);
-
你还可以通过 MonoBehaviour 或 GameObject 对象扩展获得 token,该token在 MonoBehaviour 销毁时会自动调用取消
await UniTask.DelayFrame(1000, cancellationToken: this.GetCancellationTokenOnDestroy());
-
取消的内部实现是抛出 OperationCanceledException,因此外部可以通过捕获该异常来处理取消逻辑
参考下面的异常处理
超时处理
-
正常处理方式
var cts = new CancellationTokenSource(); // unitask扩展了 CancelAfterSlim,因此unity中建议用 CancelAfterSlim,标准c#中用 CancelAfter // 使用 CancelAfterSlim 必须保存返回值,并在异步结束调用 dispose 来停止定时器 IDisposable timer = cts.CancelAfterSlim(TimeSpan.FromSeconds(5)); // 一般外部会传入一个 cancelToken,把2个token组合起来 // 其实我们可以直接用 cancelToken.CancelAfterSlim(1000) ,这样就不需要串联token, // 但是有2个问题,所以放弃该方案 // 一是无法区分是超时还是取消, // 二是超时取消后,cancelToken.IsCancellationRequested 将返回true,你别的地方不能用这个来判断是否已取消 // 注意不能直接用 cancelToken.CancelAfter,因为CancelAfter无法停止定时器 var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancelToken, cts.Token); try { await UnityWebRequest.Get("http://foo").SendWebRequest().WithCancellation(linkedTokenSource.Token); } catch (OperationCanceledException ex) { if (cancelToken.IsCancellationRequested) { UnityEngine.Debug.Log("Cancel."); } else { UnityEngine.Debug.Log("Timeout"); } } finally { timer.dispose(); // 销毁定时器 linkedTokenSource.Dispose(); cts.Dispose(); }
-
unitask提供的简便处理方式(不建议用这个,不方便跟其它 cancelToken 串联,可以用自己写的 TimeoutTokenSource)
-
CancellationTokenSource 的设计初衷是不能重复使用,也就是只要调用过Cancel就表示结束,要用再创建
而 unitask 扩展的 CancelAfterSlim 是设计为可重复使用的,为解决这个矛盾创建了 TimeoutController -
使用 TimeoutController 的好处
可以反复使用,不用重新创建
只需要的销毁的地方调用 timeoutController.Dispose() 就会触发取消,不用组合另一个 CancellationTokenSource -
缺点
不好跟其它 Token 串联,因为构造函数需要的是 TokenSource,其实内部也只用到Token,不知道为什么要传入 TokenSource
TimeoutController timeoutController = new TimeoutController(); CancellationTokenSource clickCancelSource = new CancellationTokenSource(); //跟别的取消组合,注意这里需要 CancellationTokenSource ,而不是 Cance
-