WPF 多线程 UI:设计一个异步加载 UI 的容器
对于 WPF 程序,如果你有某一个 UI 控件非常复杂,很有可能会卡住主 UI,给用户软件很卡的感受。但如果此时能有一个加载动画,那么就不会感受到那么卡顿了。UI 的卡住不同于 IO 操作或者密集的 CPU 计算,WPF 中的 UI 卡顿时,我们几乎没有可以让 UI 响应的方式,因为 WPF 一个窗口只有一个 UI 线程。
No!WPF 一个窗口可以不止一个 UI 线程,本文将设计一个异步加载 UI 的容器,可以在主线程完全卡死的情况下显示一个加载动画。
本文是对我另一篇博客 WPF 同一窗口内的多线程 UI(VisualTarget) 的一项应用。阅读本文,你将得到一个 UI 控件 AsyncBox
,放入其中的控件即便卡住主线程,也依然会有一个加载动画缓解用户的焦虑情绪。
异步加载的效果预览
下图的黑屏部分是正在加载一个布局需要花 500ms 的按钮。我们可以看到,即便是主线程被占用了 500ms,依然能有一个加载动画缓解用户的等待焦虑。
▲ 异步加载效果预览
使用我写的 WPF 异步加载控件 AsyncBox
控件的名字为 AsyncBox
,意为异步加载显示 UI 的容器。如果要使用它,可以很简单地写出以下代码:
<ww:AsyncBox LoadingViewType="demo:LoadingView">
<demo:LongTimeView />
</ww:AsyncBox>
其中,LoadingView
是在指定用哪一个控件来做加载动画。由于这个控件会在后台线程创建并执行,为了避免意外的线程问题,这里传入类型,而不是实例。
LongTimeView
是一个用来模拟耗时 UI 的模拟控件。
如果要看整个窗口,则是下面这样:
<Window x:Class="Walterlv.Demo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Walterlv.Demo"
xmlns:ww="clr-namespace:Walterlv.Windows;assembly=Walterlv.Windows"
xmlns:demo="clr-namespace:Walterlv.Demo"
Title="walterlv.com" Height="450" Width="800"
Background="Black">
<Grid>
<ww:AsyncBox LoadingViewType="demo:LoadingView">
<demo:LongTimeView />
</ww:AsyncBox>
</Grid>
</Window>
LongTimeView
则是这样:
<UserControl x:Class="Walterlv.Demo.LongTimeView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Walterlv.Demo"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
FontSize="48" FontFamily="Monaco">
<Grid>
<Button Content="walterlv.com" Click="DelayButton_Click" />
</Grid>
</UserControl>
using System.Threading;
using System.Windows;
using System.Windows.Controls;
namespace Walterlv.Demo
{
public partial class LongTimeView : UserControl
{
public LongTimeView()
{
InitializeComponent();
}
protected override Size MeasureOverride(Size constraint)
{
Thread.Sleep(500);
return base.MeasureOverride(constraint);
}
private void DelayButton_Click(object sender, RoutedEventArgs e)
{
Thread.Sleep(3000);
}
}
}
而 LoadingView
则很简单,只是一个无限旋转的动画而已。同时它还没有后台代码:
▲ LoadingView 的动画效果
<UserControl x:Class="Walterlv.Demo.LoadingView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Walterlv.Demo"
mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800">
<FrameworkElement.Resources>
<Storyboard x:Key="Storyboard.Loading">
<DoubleAnimation Storyboard.TargetName="Target"
Storyboard.TargetProperty="(UIElement.RenderTransform).(RotateTransform.Angle)"
From="0" To="1440" Duration="0:0:1.5" RepeatBehavior="Forever">
</DoubleAnimation>
</Storyboard>
</FrameworkElement.Resources>
<Grid>
<Ellipse x:Name="Target" Width="48" Height="48" Stroke="White" StrokeThickness="8"
StrokeDashArray="10" StrokeDashCap="Round" RenderTransformOrigin="0.5 0.5">
<Ellipse.RenderTransform>
<RotateTransform />
</Ellipse.RenderTransform>
<Ellipse.Triggers>
<EventTrigger RoutedEvent="FrameworkElement.Loaded">
<BeginStoryboard Storyboard="{StaticResource Storyboard.Loading}" />
</EventTrigger>
</Ellipse.Triggers>
</Ellipse>
</Grid>
</UserControl>
现在,我们来实现这个异步加载 UI 的容器
你需要为你的项目添加以下文件:
其中,1、2、3、4、6 这几个文件可分别从以下链接找到并下载到你的项目中:
- Annotations.cs
- AwaiterInterfaces.cs
- DispatcherAsyncOperation.cs
- UIDispatcher.cs
- VisualTargetPresentationSource.cs
这些文件都是通用的异步类型。
第 5 个文件 AsyncBox
就是我们要实现的主要类型。
实现思路是建一个 PresentationSource
(类似于窗口的根 HwndSource
),这可以用来承载一个新的可视化树(Visual Tree)。这样,我们就能在一个窗口中显示两个可视化树了。
这两个可视化树通过 HostVisual
跨线程连接起来,于是我们能在一个窗口中得到两个不同线程的可视化树。
由于这两棵树不在同一个线程中,于是主线程即便卡死,也不影响后台用来播放加载动画的线程。
附 AsyncBox 的源码
如果你不能在下面看到 AsyncBox
的源码,那么你的网络应该是被屏蔽了,可以访问 AsyncBox.cs - A UI container for async loading. 查看。
WPF 多线程 UI:设计一个异步加载 UI 的容器的更多相关文章
- AsyncTask使用实例,异步加载图片
在上一篇,详细介绍了AsynTask的基础知识.没有读过的朋友可以点击下面的链接: http://www.cnblogs.com/fuly550871915/p/4892310.html 那么在这篇文 ...
- cocos2d-x lua中实现异步加载纹理
原文地址: http://www.cnblogs.com/linchaolong/p/4033118.html 前言 问题:最近项目中需要做一个loading个界面,界面中间有一个角色人物走动的 ...
- AssetBundle异步加载被中断的问题
刘 刘泰言创建于 1 年前 在使用异步接口 yield return AssetBundle.ASyncLoad的时候,难免会想到:这个异步处理完之前如何Cancel掉这个任务?也就是一个AssetB ...
- android 异步加载框架 原理完全解析
一.手写异步加载框架MyAsycnTask(核心原理) 1.我为大家手写了一个异步加载框架,涵盖了异步加载框架核心原理. MyAsycnTask.java import android.os.Hand ...
- Android ListView 图片异步加载和图片内存缓存
开发Android应用经常需要处理图片的加载问题.因为图片一般都是存放在服务器端,需要联网去加载,而这又是一个比较耗时的过程,所以Android中都是通过开启一个异步线程去加载.为了增加用户体验,给用 ...
- js的异步加载你真的懂吗
面试高频之js的异步加载 讲这个问题之前, 我们从另一个面试高频问题来切入, 我们的web页面从开始解析到页面渲染完成都经历了什么 ? 1 , 创建document对象, 开始解析页面, ...
- iScroll.js 向上滑动异步加载数据回弹问题
iScroll是一款用于移动设备web开发的一款插件.像缩放.下拉刷新.滑动切换等移动应用上常见的一些效果都可以轻松实现. 现在最新版本是5.X,官网这里:http://iscrolljs.com/ ...
- vue 里面异步加载高德地图
前言 关于Vue 里面使用异步加载高德地图 项目中其实只有几处需要用到地图,不需要全局引入 在index文件中引入js会明显拖慢首屏加载速度,虽然可以使用异步加载script的方式解决,但是始终觉得不 ...
- 【转】【玩转cocos2d-x之二十三】多线程和同步03-图片异步加载
原创作品,转载请标明:http://blog.csdn.net/jackystudio/article/details/15334159 cocos2d-x中和Android,Windows都 一样, ...
随机推荐
- 5.2 Components — Defining A Component
一.概述 1. 为了定义一个组件,创建一个模板,它的名字以components/开头.为了定义一个新组件{{blog-post}},例如,创建一个components/blog-post模板. 2.注 ...
- BackgroundWorker+ProgressBar+委托 实现多线程、进度条
上文在<C# 使用BackgroundWorker实现WinForm异步>介绍了如何通过BackgroundWorker实现winForm异步通信,下面介绍如何通过BackgroundWo ...
- SpringBoot 通过自定义注解实现AOP切面编程实例
一直心心念的想写一篇关于AOP切面实例的博文,拖更了许久之后,今天终于着手下笔将其完成. 基础概念 1.切面(Aspect) 首先要理解‘切’字,需要把对象想象成一个立方体,传统的面向对象变成思维,类 ...
- RabittMQ实践(一): RabbitMQ的安装、启动
安装: 启动监控管理器:rabbitmq-plugins enable rabbitmq_management 关闭监控管理器:rabbitmq-plugins disable rabbitmq_ ...
- FTRL与Online Optimization
1. 背景介绍 最优化求解问题可能是我们在工作中遇到的最多的一类问题了:从已有的数据中提炼出最适合的模型参数,从而对未知的数据进行预测.当我们面对高维高数据量的场景时,常见的批量处理的方式已经显得力不 ...
- 网关服务Spring Cloud Gateway(一)
Spring 官方最终还是按捺不住推出了自己的网关组件:Spring Cloud Gateway ,相比之前我们使用的 Zuul(1.x) 它有哪些优势呢?Zuul(1.x) 基于 Servlet,使 ...
- MySQL的GTID复制
从mysql5.6开始引入全局事务标识符(GTID),即每个事务都有一个唯一的标识符.服务器上的每个事务都被分配一个唯一的事务标识符,这是一个64位非零的数值,根据事务提交的顺序分配.GTID的构成是 ...
- 【javascript】浏览器用户代理检测脚本实现
以下是完整的用户代理字符串检测脚本,包括检测呈现引擎.平台.Windows操作系统.移动设备和游戏系统. var client = function(){ // 呈现引擎 var engine = { ...
- 什么是OPTEE-OS
1. 为什么会出现这种技术? 为了安全,例如:保护指纹虹膜的生物特征数据 2. 为了确保数据安全各家公司都做了些什么? Arm公司提出的了trustzone技术,用一根安全总线(称为NS位)来判断当前 ...
- 分布式Redis主备复制
当数据落在不同节点上时,如何保证数据节点之间的一致性是非常关键的 Redis采用主备复制的方式保证一致性,所有节点中,只有一个节点为主节点(master),它对外提供写服务,然后异步的将数据复制到其他 ...