一、问题及疑问
先说事情起因:
public async Task<T> OpenWindow<T>(string assetName) where T : MonoBehaviour
{
return await this.OpenWindow<T>(assetName, null);
}
项目用这个异步的方法来打开UI页面,之前用一直没有遇到问题。后来写了这么个方法来调用它:
private void OnBtnClick()
{
Task<View> task = GameEntry.UI.OpenWindow<View>("CharacterPanel");
_view = task.Result;
}
但其实这个方法在unity editor(2019.4版本)上跑也是没问题的,直到打了个win包做测试。发现一旦点击按钮调用这个方法,程序就会处于无响应阶段。后来,加了一行代码程序就能正常跑了:
private async void OnBtnClick()
{
Task<View> task = GameEntry.UI.OpenWindow<View>("CharacterPanel");
//添加了这一行
await task;
_view = task.Result;
}
就感到比较疑惑,因为MSDN上是这么写的:
那也就是说在访问task.Result时,如果task还没执行完,那么调用线程是会在这里等待的。那为什么非要显式的加上 “await task;”?
二、查阅资料
在stackoverflow上问了以后,想到可以从.Net和unity 的Task不同之处入手,后来查资料时发现了这篇文章(https://zhuanlan.zhihu.com/p/86168785),发现文章里的代码和我的很相似,所以初步确定我这里应该也是发生了死锁。死锁的原理在文章里已经说得很清楚了,就不重复了。
那接下来的问题就是 await和task.Result有什么区别,为什么使用await就不会产生死锁? What is the difference between await Task and Task.Result?这个问题下的高赞回答说的很简洁:task.Result 会阻塞当前线程来等待task结束(或者说是同步阻塞);而await 则是异步等待。所以await不会造成死锁。
那就又回到最开始的地方,为什么在unity editor模式下没有产生死锁?首先,测试了上面文章里的代码,的确会造成unity卡死,说明unity editor不具备自动打破死锁的能力。那也就是说原本的代码在unity中不会产生死锁而打成win包以后会发生死锁?所以应该继续去看unity和C#在task上的不同之处…
补:又看了些资料,发现这种先调用await task do something,然后直接访问task.Result是一个“标准”的死锁模型。但是在unity里就是没有卡住啊,摔!然后,unity的异步和C#相比并没有什么特殊的操作(也可能是我没查到),那唯一的解释就是unity editor模式下执行的比较快,在访问task.Result的时候,task已经执行完了,所以没有产生死锁。
放一篇MSDN的文档,顺便记几句话:异步和同步操作不要混用;异步方法async最好不要是void类型,因为void类型在try catch的时候会对抛出的异常进行包装,影响对异常的识别与定位。