起因

最近在油管上看到了一个关于Async 和 Coroutines的对比,刚好中间有一个例子,使用Async解决双指同时点击两个不同的Button,会触发两段逻辑的Bug,而且整段代码写起来非常快乐

优缺点对比

返回值功能对比

同步功能对比

try catch 功能对比

调用堆栈功能对比

Exception 隐藏功能对比

exit 功能对比

生命周期对比

Coroutine 的生命周期取决于MonoBehavior
Async 的声明周期需要手动控制

所以引出了Cancel Task的逻辑

需要注意的是在还没有执行到Cancel的时候,这个GameObject 被销毁了,这个时候依然无法取消

PopUp Window 例子

效果展示

这里没有做窗口,只是单纯展示逻辑

  • 点击Test 开启监听,之后点击Accept返回True,点击Cancel或X,返回False
  • 点击Test 开启监听,之后点击CancelTest,取消刚刚的Task
  • 无论执行结果是什么,或者取消,都会输出Finally

视频中的例子还包含返回的Button,这里的例子就不包含这个部分了

2019-10-05 at 11.45 P

UI Async 代码

利用Async 可以天然解决掉双指触摸Button的Bug,之前需要写一堆控制,非常不快乐

下面的代码用来举例说明具体调用,在项目中还需要另外调整

public class AsyncTest : MonoBehaviour {
    public Button ButtonAccept;
    public Button ButtonCancel;
    public Button ButtonClose;
    private CancellationTokenSource cts;

    public async Task<bool> Spawn(CancellationToken ct) {
        using(var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(ct)){
            try{
                CancellationToken linkedCt   = linkedCts.Token;
                var               buttonTask = Utils.SelectButton(linkedCt, ButtonAccept, ButtonCancel, ButtonClose);
                await buttonTask;
                linkedCts.Cancel();

                return buttonTask.Result == ButtonAccept;
            }
            catch(OperationCanceledException e){
                Debug.Log("Cancel Operation");
                return false;
            }
            finally{
                linkedCts.Dispose();
                Debug.Log("Finally");
            }
        }
    }


    public async void TestButton() {
        cts = new CancellationTokenSource();
        bool result = await Spawn(cts.Token);

        Debug.Log(result);
    }

    public void CancelTestButton() {
        cts.Cancel();
        Debug.Log("Cancel");
    }
}

public static class Utils {
    public static async Task<Button> SelectButton(CancellationToken ct, Button accept, Button cancel, Button close) {
        bool   isSelect    = false;
        Button selectedBtn = null;

        accept.onClick.AddListener(
            () => {
                isSelect    = true;
                selectedBtn = accept;
            }
        );

        cancel.onClick.AddListener(
            () => {
                isSelect    = true;
                selectedBtn = cancel;
            }
        );

        close.onClick.AddListener(
            () => {
                isSelect    = true;
                selectedBtn = close;
            }
        );

        return await Task.Run(
            () => {
                while(true){
                    if(ct.IsCancellationRequested) ct.ThrowIfCancellationRequested();
                    if(isSelect) return selectedBtn;
                }
            },
            ct
        );
    }
}

视频参考

Best practices: Async vs. coroutines - Unite Copenhagen


What doesn’t kill you makes you stronger.