积极答复者
【WP8.1 Runtime】如何等待WebView.InvokeScriptAsync的执行结果?

问题
-
因为InvokeScript已经被标注为过时了,所以我尝试改用InvokeScriptAsync,但是改成异步方法的话,使用await就需要改变我这个方法的返回类型。
例如我之前的:
public string GetWidth() { WebView webView = new WebView(); return webView.InvokeScript("eval", new string[]{"window.screen.width.toString()"}); }
现在我需要改成InvokeScriptAsync,并且保持该方法的签名不变(即返回类型仍然是String类型)。于是尝试以下代码:
public string GetWidth() { WebView webView = new WebView(); var task = webView.InvokeScriptAsync("eval", new string[]{"window.screen.width.toString()"}).AsTask(); task.Wait(); return task.Result; }
但是程序在执行Wait这一句后,就卡在那里了。
请问有什么办法可以解决吗?
答案
-
你好 h82258652,
可以尝试这么做:
public async string GetWidth() { WebView webView = new WebView(); string result = await webView.InvokeScriptAsync("eval", new string[]{"window.screen.width.toString()"}); return result; }
--James
We are trying to better understand customer views on social support experience, so your participation in this interview project would be greatly appreciated if you have time. Thanks for helping make community forums a great place.
Click HERE to participate the survey.
正如版主Jamles Hez所说,改为上述代码即可解决问题。但究竟为什么会在Wait()那里卡住?
因为你的这两句代码产生了UI线程的死锁。如下图中的解释:
更多解释请参考http://www.cppblog.com/weiym/archive/2012/12/16/196364.html
全部回复
-
你好 h82258652,
可以尝试这么做:
public async string GetWidth() { WebView webView = new WebView(); string result = await webView.InvokeScriptAsync("eval", new string[]{"window.screen.width.toString()"}); return result; }
--James
We are trying to better understand customer views on social support experience, so your participation in this interview project would be greatly appreciated if you have time. Thanks for helping make community forums a great place.
Click HERE to participate the survey.- 已编辑 Jamles HezModerator 2015年6月3日 3:26
-
你好 h82258652,
可以尝试这么做:
public async string GetWidth() { WebView webView = new WebView(); string result = await webView.InvokeScriptAsync("eval", new string[]{"window.screen.width.toString()"}); return result; }
--James
We are trying to better understand customer views on social support experience, so your participation in this interview project would be greatly appreciated if you have time. Thanks for helping make community forums a great place.
Click HERE to participate the survey.
正如版主Jamles Hez所说,改为上述代码即可解决问题。但究竟为什么会在Wait()那里卡住?
因为你的这两句代码产生了UI线程的死锁。如下图中的解释:
更多解释请参考http://www.cppblog.com/weiym/archive/2012/12/16/196364.html
-
因为InvokeScript已经被标注为过时了,所以我尝试改用InvokeScriptAsync,但是改成异步方法的话,使用await就需要改变我这个方法的返回类型。
例如我之前的:
public string GetWidth() { WebView webView = new WebView(); return webView.InvokeScript("eval", new string[]{"window.screen.width.toString()"}); }
现在我需要改成InvokeScriptAsync,并且保持该方法的签名不变(即返回类型仍然是String类型)。于是尝试以下代码:
public string GetWidth() { WebView webView = new WebView(); var task = webView.InvokeScriptAsync("eval", new string[]{"window.screen.width.toString()"}).AsTask(); task.Wait(); return task.Result; }
但是程序在执行Wait这一句后,就卡在那里了。
请问有什么办法可以解决吗?
没有办法解决。因为异步操作是在另一个线程中进行的,所以最终无论如何都需要一个同步操作才能将数据从异步线程同步回UI线程。所以很显然异步操作是不可能改为同步操作的,必须有线程同步这一步骤。当你从UI线程中启动一个后台线程后,UI线程要么锁定挂起,要么继续执行,实际上任何线程都是如此,只是微软禁止锁定UI线程,只允许在后台线程中锁定,也就是说不允许出现UI无响应这一现象。结论就是返回值类型只能改为async string,并级联改变所有调用链,直到数据同步回UI线程为止。
Shi Xin
Picture是一款免费图片浏览器,完全支持触摸操作,赶快来下载吧。
hi Shi Xin
根据你的解释还是不能够很好的去理解。
仿照h82258652的问题,我写了两段代码来测试
代码段一:
private async void Button_Click1(object sender, RoutedEventArgs e) { IRandomAccessStream stream = await (await StorageFile.GetFileFromApplicationUriAsync(new Uri("ms-appx:///Assets/1.jpg"))).OpenAsync(FileAccessMode.Read); string b = Test1(stream); Debug.WriteLine(b); } public string Test1(IRandomAccessStream stream) { BitmapImage bitmapImage = new BitmapImage(); var task = bitmapImage.SetSourceAsync(stream).AsTask(); task.Wait(); return "OK"; }
上面这段代码一样会产生死锁。
代码段二:
private void Button_Click(object sender, RoutedEventArgs e) { int a = Test(); Debug.WriteLine(a); } public int Test() { IAsyncOperation<int> action = AsyncInfo.Run<int>( (token) => Task.Run<int>( () => { return 10 * 10; }, token)); var task = action.AsTask(); task.Wait(); return task.Result; }
这段代码就没有产生死锁。
根据我的理解,可不可以这样去解释。凡是由工作在UI线程的控件开启的线程task,如果Wait,那就是UI直接wait了,界面卡死。如果只是一个简单的线程,则wait不会造成死锁。
不知我这样的解释合不合理。
麻烦Shi Xin或者Jamles Hez版主将这个产生死锁的原理通俗的说明一下,以便大家更好的理解。
谢谢~
-
你好 h82258652,
可以尝试这么做:
public async string GetWidth() { WebView webView = new WebView(); string result = await webView.InvokeScriptAsync("eval", new string[]{"window.screen.width.toString()"}); return result; }
--James
We are trying to better understand customer views on social support experience, so your participation in this interview project would be greatly appreciated if you have time. Thanks for helping make community forums a great place.
Click HERE to participate the survey.
Jamles 你好,你确定这段代码可行吗?我一直在用C++,所以C#不是很熟,所以我不确定你的代码是不是正确的。不过在我这里它无法通过编译,会显示“异步方法的返回类型必须为 void、Task 或 Task<T>”。
Shi Xin
Picture是一款免费图片浏览器,完全支持触摸操作,赶快来下载吧。
hi Shi Xin
Jamles的方法写法有误,应该是下面这段:
public async Task<string> GetWidth() { WebView webView = new WebView(); string result = await webView.InvokeScriptAsync("eval", new string[]{"window.screen.width.toString()"}); return result; }
-
Andy 你好,我上面并没有解释关于死锁的问题,我只是说微软政策上不允许UI停止响应,这个不是技术问题,而是审核问题。也就是说为什么不能把异步函数改为同步函数,因为这会导致UI停止响应,或者说暂停响应。不过既然你提到了死锁,我就解释一下。
为什么你的第二段代码没有死锁,因为你的异步线程,或者说后台线程没有等待UI线程。而第一段代码中的异步函数中可能已经包括了与UI线程同步的代码,但由于UI线程已被上锁,所以这就形成了互锁,也就是死锁,所以程序就挂了。至于一个异步函数中是否包括与UI线程同步的代码,调用方无从得知,因为函数已经被封装了。因此不要在UI线程上加锁,而是等待后台线程完成后再同步。
补充:.Net的一个缺点就是代码写起来很简单,但逻辑上却不如C++那样清晰。如果你使用C++来写的话,应该就不会在这方面有什么困惑了,有时间的话不妨看看C++的代码。
Shi Xin
Picture是一款免费图片浏览器,完全支持触摸操作,赶快来下载吧。
hi Shi Xin
受到你的启发,我对第二段代码做了下修改,达到了死锁的效果,终于明白为什么死锁了。
第二段代码修改后如下:
private void Button_Click(object sender, RoutedEventArgs e) { int a = Test(); Debug.WriteLine(a); } public int Test() { IAsyncOperation<int> action = AsyncInfo.Run<int>( (token) => Task.Run<int>( async () => { await Task.Delay(5000); Debug.WriteLine("测试"); return 10 * 10; }, token)); var task = action.AsTask(); task.Wait(); return task.Result; } }
死锁产生过程解释如下:
首先调用task.Wait(),UI线程会锁定挂起,等待task执行完成后返回。但task里面有一个同步到UI线程的await Task.Delay(5000),但此时UI线程已经锁定挂起,task无法继续同步到UI线程,即task无法继续执行,会一直卡在await Task.Delay(5000)。这样的结果就是UI线程等待task返回同步结果,task内部却等待同步到UI线程而无法返回结果。相互等待,死锁就产生了。
感谢 Shi Xin,让我有了更深的理解。
- 已编辑 Andy_Li_ 2015年6月3日 11:12
-
你好 Shi Xin, Andy
的确,如果要把异步方法改成同步方法的话,需要更改整个调用链,不好意思我代码写错了,应该是Task<string>,写代码的时候没有检查,sorry。
异步是开一个独立的线程来执行代码,微软开异步的理由是有可能代码执行时间大于50ms, 所以为了不影响UI才重新开辟一条线程去执行。上面Shi Xin解释的很清楚了,我没啥要补充的。
再次感谢大家对论坛的支持 :)
--James
We are trying to better understand customer views on social support experience, so your participation in this interview project would be greatly appreciated if you have time. Thanks for helping make community forums a great place.
Click HERE to participate the survey.