前言
对比协程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,这里面封装了很多原来协程支持的类型,基本上可以做到无缝切换