WinRT – 在保持UI响应的同时加载数据
我正在开发一个Windows Metro应用程序,并且我遇到了UI无法响应的问题。 据我所知,原因如下:
<ListView ... SelectionChanged="ItemListView_SelectionChanged" ...
此事件在此处理:
async void ItemListView_SelectionChanged(object sender, SelectionChangedEventArgs e) { if (this.UsingLogicalPageNavigation()) this.InvalidateVisualState(); MyDataItem dataItem = e.AddedItems[0] as MyDataItem; await LoadMyPage(dataItem); } private async Task LoadMyPage(MyDataItem dataItem) { SyndicationClient client = new SyndicationClient(); SyndicationFeed feed = await client.RetrieveFeedAsync(new Uri(FEED_URI)); string html = ConvertRSSToHtml(feed) myWebView.NavigateToString(html, true); }
LoadMyPage
需要一段时间才能完成,因为它从Web服务获取数据并将其加载到屏幕上。 然而,看起来UI正在等待它:我的猜测是,直到上述事件完成。
所以我的问题是:我能做些什么呢? 是否有一个更好的事件我可以挂钩,还是有另一种方法来处理这个? 我考虑过开始一个后台任务,但这对我来说似乎有些过分。
编辑:
只是为了澄清这个问题的规模,我说的是最多3到4秒没有反应。 这绝不是一项长期工作。
编辑:
我已经尝试了下面的一些建议,但是, SelectionChanged
函数中的整个调用堆栈正在使用async / await。 我已经跟踪了这句话:
myFeed = await client.RetrieveFeedAsync(uri);
在完成之前似乎没有继续处理。
编辑:
我意识到这正在变成战争与和平,但下面是使用空白地铁应用程序和按钮复制问题:
XAML:
代码背后:
private async void Button_Click_1(object sender, RoutedEventArgs e) { SyndicationFeed feed = null; SyndicationClient client = new SyndicationClient(); Uri feedUri = new Uri(myUri); try { feed = await client.RetrieveFeedAsync(feedUri); foreach (var item in feed.Items) { test.Text += item.Summary.Text + Environment.NewLine; } } catch { test.Text += "Connection failedn"; } }
尝试一下……
SyndicationFeed feed = null; SyndicationClient client = new SyndicationClient(); var feedUri = new Uri(myUri); try { var task = client.RetrieveFeedAsync(feedUri).AsTask(); task.ContinueWith((x) => { var result = x.Result; Parallel.ForEach(result.Items, item => { Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () => { test.Text += item.Title.Text; }); }); }); } catch (Exception ex) { }
我在我的机器上尝试使用Grid应用程序模板向应用程序添加按钮。 我可以来回滚动项目网格,同时更新页面标题没有问题。 虽然我没有很多物品,但它的速度非常快,所以很难100%正面。
因为你在LoadMyPage
前面使用await
我假设它编译并且它返回一个Task
。 鉴于此,我创造了一个小例子。
我们假设LoadMyPage
(和Sleep()
)看起来像这样:
public Task LoadMyPage() { return Task .Factory.StartNew(() => { Sleep(3000); return "Hello world"; }); } static void Sleep(int ms) { new ManualResetEvent(false).WaitOne(ms); }
并且XAML
看起来像这样:
Test Test2
然后,我们可以将SelectionChanged
事件处理程序看起来像这样:
private async void ItemListView_SelectionChanged(object sender, SelectionChangedEventArgs e) { MyList.IsEnabled = false; var result = await LoadMyPage(); Result.Text = result; MyList.IsEnabled = true; }
LoadMyPage
返回的Task
将并行运行,这意味着当该任务正在运行时, UI
不应该冻结。 现在,要从该Task
获取结果,请使用await
。 这将创建一个延续块。
因此,在此示例中,当您选择某些内容时, ListView
将在整个加载时间内禁用,然后在Task
完成后重新启用。 您可以通过按下按钮来确认UI没有冻结,以确定它仍然是响应式的。
如果LoadMyPage
与UI交互,则需要重新排列它,让它返回ViewModel
或您想要的结果,然后将所有内容再次放在UI线程上。
背景线程绝对不是矫枉过正。 这正是你如何处理这类问题。
不要在UI线程上执行冗长的任务,否则您将占用UI并使其无响应。 在后台线程上运行这些,然后让该线程引发一个事件,该事件可以在主UI线程完成时由主UI线程处理。
在UI线程上显示某种进度指示器也很有用。 用户喜欢知道发生了什么事 。 这将使他们放心,应用程序没有被破坏或冻结,他们愿意等待一段时间。 这就是为什么所有Web浏览器都有某种“悸动”或其他加载指示器的原因。
最可能的问题是LoadMyPage
正在同步执行某些操作。 请记住, async
不会在后台线程上运行您的代码; 默认情况下,它的所有实际代码都将在UI线程上运行(请参阅async / await FAQ或我的async / await intro )。 因此,如果您在异步方法中阻塞,它仍然会阻塞调用线程。
看看LoadMyPage
。 是否使用await
来调用Web服务? 在将数据放入UI之前,它是否进行了昂贵的数据处理? 是否压倒了UI(许多Windows控件在获得数千个元素时都存在可伸缩性问题)?
看看你的简化代码示例,我相信你的问题就是那些位于await行之外的东西。
在以下代码块中:
private async void Button_Click_1(object sender, RoutedEventArgs e) { SyndicationFeed feed = null; SyndicationClient client = new SyndicationClient(); Uri feedUri = new Uri(myUri); try { feed = await client.RetrieveFeedAsync(feedUri); foreach (var item in feed.Items) { test.Text += item.Summary.Text + Environment.NewLine; } } catch { test.Text += "Connection failedn"; } }
在后台线程上执行的唯一行是该行
feed = await client.RetrieveFeedAsync(feedUri);
该块中的所有其他代码行都在UI线程上执行。
仅仅因为您的按钮单击处理程序被标记为异步并不意味着其中的代码不会在UI线程上运行。 事实上,事件处理程序在UI线程上启动。 因此,创建SyndicationClient并设置Uri发生在UI线程上。
许多开发人员没有意识到的是, 在 await 之后出现的任何代码将自动在 await 之前使用的同一个线程上恢复。 这意味着代码
foreach (var item in feed.Items) { test.Text += item.Summary.Text + Environment.NewLine; }
正在UI线程上运行!
这很方便,因为您不必执行Dispatcher.Invoke来更新test.Text ,但这也意味着您在循环遍历项目和连接字符串时阻止UI线程。
在您的(尽管是简化的)示例中,在后台线程上执行此工作的最简单方法是在SyndicationClient上使用另一个名为RetrieveFeedAsStringAsync的方法; 然后,SyndicationClient可以将字符串的下载,循环和串联作为其自身任务的一部分。 该任务完成后,将在UI线程上运行的唯一代码行将文本分配给TextBox。
上述就是C#学习教程:WinRT – 在保持UI响应的同时加载数据分享的全部内容,如果对大家有所用处且需要了解更多关于C#学习教程,希望大家多多关注—计算机技术网(www.ctvol.com)!
本文来自网络收集,不代表计算机技术网立场,如涉及侵权请联系管理员删除。
ctvol管理联系方式QQ:251552304
本文章地址:https://www.ctvol.com/cdevelopment/1033599.html