【问题标题】:Why is this async method blocking the UI thread?为什么这个异步方法会阻塞 UI 线程?
【发布时间】:2013-06-20 23:44:24
【问题描述】:

我正在努力解决这段代码引发的问题:

    private int FPS = 60;

    void WebView_LoadCompleted(object sender, NavigationEventArgs e)
    {
        WebviewContentWorker();
    }

    private async void WebviewContentWorker()
    {
        WebViewBrush wvb = new WebViewBrush();
        wvb.SetSource(WebView);
        wvb.Redraw(); //we must redraw at least once before collapsing the WebView
        WebView.Visibility = Windows.UI.Xaml.Visibility.Collapsed;

        while (true)
        {
            webViewContent.Background = wvb; //webViewContent is a canvas
            await Task.Delay(1000 / FPS);
            wvb.Redraw();
        }
    }

我在这里想要实现的是为 XAML 的WebView 找到一种解决方法,我觉得这很草率。我希望能够在上面画东西,但我不能,所以我基本上做的是重复拍摄WebView(使用WebViewBrush)的快照(基于int FPS字段)和然后使用此快照设置名为“webViewContent”的画布的Background 属性。目的是在画布上显示动画,同时仍然能够在其上进行绘制(如果我不做这些快速快照,画布将显示静止图像)。

它现在工作正常(我成功地将任何Tapped 事件重定向到WebView 内部,以便正确处理对按钮/链接/...的点击),但它有点滞后。慢一点是wvb.Redraw() 我想知道如何提高线程的性能。看起来 UI 在Task.Delay 期间响应,但在其他情况下被阻止...

非常欢迎任何意见/建议!

编辑: 以下是我对Redraw 调用的计时方式(我认为这是导致问题的原因,因为删除它会使应用程序非常敏感):

        while (true)
        {
            webViewContent.Background = wvb;
            await Task.Delay(1000 / FPS);
            sw.Reset();
            sw.Start();
            wvb.Redraw();
            sw.Stop();
            System.Diagnostics.Debug.WriteLine(sw.Elapsed.TotalMilliseconds);
        }

这在输出窗口中给了我这些结果:

0,094
0,058
0,041
0,053
0,057
0,038
0,032
0,033
0,032
0,038
0,035
0,03
0,042
0,028
0,044
0,031
0,033
0,029
0,034
0,03
0,052
0,029

毕竟不是那么多......

【问题讨论】:

  • 您的Redraw 方法运行大约需要多长时间?另外,60 FPS 真的有必要吗?你能把它调低一点,显示器不会太生涩吗?
  • 我今天尝试使用StopWatch ealier,我相信(double) Elapsed.TotalMilliseconds 属性返回的值介于0,1XXX 和0,3XXX 之间。
  • 如果它需要 1/3 毫秒,那么即使每秒执行 60 次,我也看不出它会如何阻塞 UI 足够长的时间以被人察觉。这仍然是 UI 的消息循环保持响应的充足时间。也许您没有在实际使用它的相同条件下正确计时。
  • 我用正确的结果编辑了我的第一篇文章以及我是如何计时的,看看吧!

标签: c# multithreading windows-runtime async-await


【解决方案1】:

看起来 UI 在 Task.Delay 期间响应,但在其他情况下被阻止...

嗯,是的。 正是正在发生的事情。 Task.Delay 是您让 UI 线程工作的唯一机会。您的异步方法正在 UI 线程上执行 - 一旦“延迟”任务完成,您最终将继续等待在 UI 线程上执行,该线程将重绘然后再次延迟。

从根本上说,如果您的 Redraw 方法太慢而无法每秒调用约 60 次,您需要一种不同的方法。

了解async 不会将方法放到不同的线程上非常重要——它只是允许您异步执行。 (您的描述和标题表明您希望您的方法在任何重要时间都不会使用 UI 线程。)

此外,正如 Stephen Cleary 所说,使用 DispatcherTimer 通常是在 UI 线程上定期执行代码的更好方法。

【讨论】:

  • 感谢您的解释,我确实认为async 在不同的线程上自动构建了一个FSM。您写过关于寻找不同方法的文章,您对此有什么线索或想法吗?
  • @RedPolygon:基本上你需要让Redraw 更快或者更少执行它——就这么简单。或者,如果可以的话,也可以将一些重绘工作放到不同的线程中,但是如果没有更多信息就很难知道。
  • 在另一个线程中做一些Redraw 的工作会很棒,但我没有编写方法,所以我会看看我是否有足够的勇气潜入 MSIL(我刚开始学习 WinRT) .让我们期待一个更好的WebView会和8.1一起发布,也许我们会在下周的BUILD活动中了解更多。无论如何感谢您的建议。
【解决方案2】:

Task.Delay 对于重复执行许多短超时并不是特别有效。你会产生很多垃圾。

我建议在这种情况下使用调度程序计时器或类似的。

【讨论】:

  • 虽然我同意Task.Delay 在这里并不理想,但我没想到Task.Delay 产生的垃圾量真的很大。毕竟每秒最多只能调用 60 次……而且 GC 可以处理真的相当多的垃圾……
  • 感谢您的提示,它工作得更好一些,但仍然过于滞后。我在画布顶部有一个自定义控件,你可以四处移动,你可以明确地感觉到延迟。
猜你喜欢
  • 1970-01-01
  • 2021-08-29
  • 1970-01-01
  • 1970-01-01
  • 2017-07-07
  • 2020-05-10
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多