Unity3D async和await

前言

对比协程async-await是更加更理想的异步方案。

介绍

首先,理解一下这两个关键字:

async:可以把方法标识为异步

await:异步方法在碰到await表达式之前都是使用同步的方式执行

实现原理

Unity提供了一个名为 UnitySynchronizationContext 的默认 SynchronizationContext

它会自动收集每个帧排队的任何异步代码,并在主要的Unity线程上继续运行它们。

主要有两个方法:

1.Send():是简单的在当前线程上去调用委托来实现(同步调用)。也就是在子线程上直接调用线程执行,等线程执行完成后子线程才继续执行。

2.Post():在线程池上去调用委托来实现(异步调用)。这是子线程会从线程池中找一个线程去调线程,子线程不等待UI线程的完成而直接执行自己下面的代码。

实现一个调度器实现原理如下:

主要是通过 SynchronizationContext.Current 进行上下文判断

1.如果是一个,就调用 Send 方法(或者直接调用 action)

2.如果不是一个,就调用 Post 方法

public class SyncContextUtil
{
    //RuntimeInitializeOnLoadMethod 在物体的初始化之前就调用 
    //  流程:BeforeSceneLoad -> Awake -> OnEnable ->AfterSceneLoad -> Start
    [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
    static void Install()
    {
        UnitySynchronizationContext = SynchronizationContext.Current;
        UnityThreadId = Thread.CurrentThread.ManagedThreadId;
    }

    //Unity主线程Id
    public static int UnityThreadId
    {
        get; private set;
    }

    //SynchronizationContext 线程上下文
    //主要有两个方法:
    //  1.Send():是简单的在当前线程上去调用委托来实现(同步调用)。也就是在子线程上直接调用UI线程执行,等UI线程执行完成后子线程才继续执行。
    //  2.Post():在线程池上去调用委托来实现(异步调用)。这是子线程会从线程池中找一个线程去调UI线程,子线程不等待UI线程的完成而直接执行自己下面的代码。
    public static SynchronizationContext UnitySynchronizationContext
    {
        get; private set;
    }

    //实现Unity的多线程调度器
    public static void RunOnUnityScheduler(Action action)
    {
        if (SynchronizationContext.Current == SyncContextUtil.UnitySynchronizationContext)
        {
            //本质上就是直接 action()
            //SyncContextUtil.UnitySynchronizationContext.Send(_ => action(), null);
            action();
        }
        else
        {
            SyncContextUtil.UnitySynchronizationContext.Post(_ => action(), null);
        }
    }
}

与协程的使用对比

协程的方式

IEnumerator Start()
{
    Debug.Log("等待 1 秒");
    yield return new WaitForSeconds(1.0f);
    Debug.Log("结束");
}

async await的使用方式

async void Start()
{
    Debug.Log("等待 1 秒");
    await Task.Delay(TimeSpan.FromSeconds(1f));
    Debug.Log("结束");
}

之前虽然也能够实现异步,但是在异步函数调用的时候对于函数的返回值是一无所知的。但是现在async await的方式解决了这个问题,让我们的异步请求同样可以做成线性的流程。

使用实例介绍

如果想读取SteamingAssets下面的文件,之前都是用 协程+UnityWebRequest

现在可以用

    void Start()
    {
        ReadText();
    }

    async void ReadText()
    {
        Task<string> task = AsyncLoadText("test.txt");
        await task;
        Debug.Log(task.Result);
    }

    async Task<string> AsyncLoadText(string url)
    {
        string path = Path.Combine(Application.streamingAssetsPath, url);
        UnityWebRequest getRequest = UnityWebRequest.Get(path);
        await getRequest.SendWebRequest();
        string text = getRequest.downloadHandler.text;
        return text;
    }

注意:await getRequest.SendWebRequest(); 这句话。

await 后面需要接的类型是 TaskAwaiter(INotifyCompletion),U3D最开始还没有这个语法,所以很多需要我们自己写扩展类,强行转成 TaskAwaiter 类型,让它支持这个语法。

public static class AsyncOperationExtensions
{
    public static TaskAwaiter GetAwaiter(this AsyncOperation asyncOperation)
    {
        var tcs = new TaskCompletionSource<object>();
        asyncOperation.completed += obj => { tcs.SetResult(null); };
        return ((Task)tcs.Task).GetAwaiter();
    }
}

有一个开源的项目:Unity3dAsyncAwaitUtil/releases,这里面封装了很多原来协程支持的类型,基本上可以做到无缝切换

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值