UWP开发入门(二十一)——保持Ui线程处于响应状态
GUI的程序有时候会因为等待一个耗时操作完成,导致界面卡死。本篇我们就UWP开发中可能遇到的情况,来讨论如何优化处理。
假设当前存在点击按钮跳转页面的操作,通过按钮打开的新页面,在初始化过程中存在一些耗时的操作。
public void OnNavigatedTo(object obj)
{
var watch = new Stopwatch();
Debug.WriteLine("---------------Start");
watch.Start(); //假设耗时1秒
DoBusyWork();
//耗时1秒
int count = GetPersonCount();
//假设每创建一个Person耗时500毫秒
PersonList = CreatePersonList(count); watch.Stop();
Debug.WriteLine(watch.ElapsedMilliseconds);
Debug.WriteLine("----------------Stop"); Notify = "页面初始化已完成!计时:" + watch.ElapsedMilliseconds + "毫秒";
}
可以注意到以上方法都是顺序同步执行完成的,在点击跳转按钮后,会有一个明显的卡死且非常尴尬的等待过程。GetPersonCount方法返回100这个数字的话,StopWatch记录的用时会是大约7秒,在这7秒之后才会打开跳转的页面,这是一个无法忍受的时间。
优化的初步思路是将无需等待完成的操作放到非UI线程去做。这里发现DoBusyWork这个方法是可以剥离开的。
Task.Run(()=> { DoBusyWork(); });
写完之后发现虽然减少了1秒,但是意义不大,还是很卡。而PersonList的赋值操作必须在UI线程执行,不能够用Task来放到后台,这一步的优化貌似到这里就没辙了。
接下来的思路是采用async和await这对关键字来进行异步编程,首先我们要明确使用了await的语句仍然是会阻塞并等待完成,才可以执行下一句的。不同的是程序会在await的时候yeid return一次,以使得UI线程保持响应。但错误或者不合适的使用await往往会导致意想不到的结果,甚至比同步执行更差的性能。我们先看第一版的异步程序:
public async void OnNavigatedTo(object obj)
{
var watch = new Stopwatch();
Debug.WriteLine("---------------Start");
watch.Start(); //不必要的等待,耗时1秒
await DoBusyWorkAsync();
//耗时1秒,返回数字100
int count = await GetPersonCountAsync();
//依然会造成长时间阻塞的Get方法
PersonList = await CreatePersonListAsync(count); watch.Stop();
Debug.WriteLine(watch.ElapsedMilliseconds);
Debug.WriteLine("----------------Stop"); Notify = "页面初始化已完成!计时:" + watch.ElapsedMilliseconds + "毫秒";
}
运行发现,Navigate到第二个页面很快(这是await的功劳),但是等到PersonList完全加载出来,仍然耗时7秒。这里的第一个错误是不必要的await DoBusyWorkAsync这个方法,应该果断去除await关键字,虽然Visual Studio会给出warning:“由于此调用不会等待,因此在此调用完成之前将会继续执行当前方法。请考虑将 "await" 运算符应用于调用结果”。但仔细想想会发现我们的本意就是不等待该方法。如果想去掉该提示,可以考虑将DoBusyWorkAsync方法的返回值由Task改为void。
private async void DoBusyWorkAsync()
{
await Task.Delay();
}
改为void之后在捕获异常时可能会没有堆栈信息,考虑到这里是个简单方法,就不用顾虑了。
CreatePersonListAsync方法依赖于GetPersonCountAsync的返回值,这种情况下没有太好的优化方案。只能说GetPersonCountAsync的这一秒你值得等待。
至于CreatePersonListAsync方法本身的耗时达到了5秒,成为了性能瓶颈,对该方法进行分析:
private async Task<ObservableCollection<Person>> CreatePersonListAsync(int count)
{
var list = new ObservableCollection<Person>();
for (int i = ; i < count; i++)
{
var person = await Person.CreatePresonAsync(i, i.ToString());
list.Add(person);
}
return list;
}
可以看到阻塞发生在for循环的内部,每次 await Person.CreatePresonAsync都有500毫秒的等待发生。而实际上每个create preson的操作是独立的,并不需要等待前一次的完成。代码修改如下:
private ObservableCollection<Person> CreatePersonListWithContinue(int count)
{
var list = new ObservableCollection<Person>();
for (int i = ; i < count; i++)
{
Person.CreatePresonAsync(i, i.ToString()).ContinueWith(_ => list.Add(_.Result),TaskScheduler.FromCurrentSynchronizationContext());
} return list;
}
修改后运行效果还挺不错的,首先页面间的跳转不再卡顿,同时PersonList的加载时间也有了明显的缩短,在“页面初始化已完成”这句话出现后很短的时间内,列表便加载完毕,不过仔细观察发现元素的顺序是错乱的。
这是因为for循环里CreatePersonAsync的操作相当于并发进行,添加到List里的顺序自然是不固定的。我们可以在插入前进行排序来修正。
private ObservableCollection<Person> CreatePersonListWithContinue(int count)
{
var list = new ObservableCollection<Person>();
for (int i = ; i < count; i++)
{
Person.CreatePresonAsync(i, i.ToString()).ContinueWith(_ => {
var person = _.Result;
int index = list.Count(p => p.Age < person.Age);
list.Insert(index, person);
},TaskScheduler.FromCurrentSynchronizationContext());
} return list;
}
至此程序才算有了一个比较好的效果,有两点可以总结一下:
- 通过Task.Run将非UI相关的操作运行在后台线程上,减少不必要的等待时间
- 通过将耗时操作拆分成N个await返回的异步方法,可以使UI线程保持响应
GitHub:https://github.com/manupstairs/UWPSamples/tree/master/UWPSamples/KeepUIResponsive
UWP开发入门(二十一)——保持Ui线程处于响应状态的更多相关文章
- UWP开发入门(十一)——Attached Property的简单应用
UWP中的Attached Property即附加属性,在实际开发中是很常见的,比如Grid.Row: <Grid Background="{ThemeResource Applica ...
- UWP开发入门(十七)——判断设备类型及响应VirtualKey
蜀黍我做的工作跟IM软件有关,UWP同时会跑在电脑和手机上.电脑和手机的使用习惯不尽一致,通常我倾向于根据窗口尺寸来进行布局的变化,但是特定的操作习惯是依赖于设备类型,而不是屏幕尺寸的,比如聊天窗口的 ...
- UWP开发入门(十)——通过继承来扩展ListView
本篇之所以起这样一个名字,是因为重点并非如何自定义控件,不涉及创建CustomControl和UserControl使用的Template和XAML概念.而是通过继承的方法来扩展一个现有的类,在继承的 ...
- UWP开发入门系列笔记之(一):UWP初览
标签: 随着微软Build2015带来的好消息,Win10正式版发布的日子已经离我们越来越近了,我们也终于欣喜地看到:一个统一的Windows平台对于开发人员来说充满了吸引力,这局棋下的好大的说--于 ...
- UWP开发入门(十六)——常见的内存泄漏的原因
本篇借鉴了同事翔哥的劳动成果,在巨人的肩膀上把稿子又念了一遍. 内存泄漏的概念我这里就不说了,之前<UWP开发入门(十三)——用Diagnostic Tool检查内存泄漏>中提到过,即使有 ...
- UWP开发入门(四)——自定义CommandBar
各位好,再次回到UWP开发入门系列,刚回归可能有些不适应,所以今天我们讲个简单的,自定义CommandBar,说通俗点就是自定义类似AppBarButton的东西,然后扔到CommandBar中使用. ...
- UWP开发入门(25)——通过Radio控制Bluetooth, WiFi
回顾写了许久的UWP开发入门,竟然没有讲过通过Windows.Devices.Radios.Radio来控制Bluetooth和WiFi等功能的开关.也许是因为相关的API设计的简单好用,以至于被我给 ...
- Java并发(二十一):线程池实现原理
一.总览 线程池类ThreadPoolExecutor的相关类需要先了解: (图片来自:https://javadoop.com/post/java-thread-pool#%E6%80%BB%E8% ...
- Directx11学习笔记【二十一】 封装键盘鼠标响应类
原文:Directx11学习笔记[二十一] 封装键盘鼠标响应类 摘要: 本文由zhangbaochong原创,转载请注明出处:http://www.cnblogs.com/zhangbaochong/ ...
随机推荐
- SQLServer内核架构剖析 (转载)
SQL Server内核架构剖析 (转载) 这篇文章在我电脑里好长时间了,今天不小心给翻出来了,觉得写得很不错,因此贴出来共享. 不得不承认的是,一个优秀的软件是一步一步脚踏实地积累起来的,众多优秀的 ...
- Visual Studio 2013 错误提示“未找到与约束匹配”的修正
昨天由于项目需要,在开发电脑上安装了Microsoft Office Project 2007来做时间计划,但是安装之后第二天重新打开VS之后,就无法打开项目或者原有程序文件无法打开.错图提示界面如下 ...
- 使用扩展方法简化RadAjaxManager设置
相对于RadAjaxPanel,RadAjaxManager提供了更精确控制更新目标的设置,特别是在某些场景下,使用RadAjaxManager能够获得更好的性能. 但是,由于要明确设置目标,配置的代 ...
- 高性能网站架构设计之缓存篇(3)- Redis 的配置
我们说Redis是一个强大的Key-Value存储系统,在前面我们已遇到了两个问题: 1.redis server 启动后,独占进程,能不能修改为后台服务呢? 2.redis server 服务是单线 ...
- RCP:eclipse的DEBUG机制
Eclipse debug文档翻译 运行一个程序需要添加launch configurable,在自定义launch configuration的时候会指定模式,比如run,debug,profile ...
- 实战-Fluxion与wifi热点伪造、钓鱼、中间人攻击、wifi破解
原作者:PG 整理:玄魂工作室-荣杰 目录: 0x00-Fluxion是什么 0x01-Fluxion工作原理 0x02-Kali上安装fluxion 0x03-Fluxion工具使用说明+实 ...
- 走进AngularJs(七) 过滤器(filter)
过滤器(filter)正如其名,作用就是接收一个输入,通过某个规则进行处理,然后返回处理后的结果.主要用在数据的格式化上,例如获取一个数组中的子集,对数组中的元素进行排序等.ng内置了一些过滤器,它们 ...
- js模版引擎handlebars.js实用教程——如何引入Handlebars.js
返回目录 Jquery插件,第一步当然要引用Jquery啦,然后引用Handlebars.js即可,仅仅需要这两个js文件. <script type="text/javascript ...
- STSdb,最强纯C#开源NoSQL和虚拟文件系统 4.0 RC2 支持C/S架构
STSdb是什么 再来说明一下STSdb是什么:STSdb是C#写的开源嵌入式数据库和虚拟文件系统,支持实时索引,性能是同类产品的几倍到几十倍,访问官方网站. 温故知新 之前发了文章<STSdb ...
- 实战使用Axure设计App,使用WebStorm开发(1) – 用Axure描述需求
系列文章 实战使用Axure设计App,使用WebStorm开发(1) – 用Axure描述需求 实战使用Axure设计App,使用WebStorm开发(2) – 创建 Ionic 项目 实战使 ...