原文:拒绝卡顿——在WPF中使用多线程更新UI

有经验的程序员们都知道:不能在UI线程上进行耗时操作,那样会造成界面卡顿,如下就是一个简单的示例:

public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            this.Dispatcher.Invoke(new
Action(()=> { }));
            this.Loaded += MainWindow_Loaded;
        }

        private
void MainWindow_Loaded(object sender, RoutedEventArgs e)
        {
            this.Content = new
UserControl1();
        }
    }

    class
UserControl1 : UserControl
    {
        TextBlock textBlock;

        public UserControl1()
        {
            textBlock = new
TextBlock();
            this.Content = textBlock;

            this.Dispatcher.BeginInvoke(new
Action(updateTime), null);
        }

        private
async
void updateTime()
        {
            while (true)
            {
                Thread.Sleep(900);            //模拟耗时操作

                textBlock.Text = DateTime.Now.ToString();
                await
Task.Delay(100);
            }
        }
    }

当我们运行这个程序的时候,就会发现:由于主线程大部分的时间片被占用,无法及时处理系统事件(如鼠标,键盘等输入),导致程序变得非常卡顿,连拖动窗口都变得不流畅;

如何解决这个问题呢,初学者可能想到的第一个方法就是新启一个线程,在线程中执行更新:

public UserControl1()
    {
        textBlock = new
TextBlock();
        this.Content = textBlock;

        ThreadPool.QueueUserWorkItem(_ => updateTime());
    }

但很快就会发现此路不通,因为WPF不允许跨线程访问程序,此时我们会得到一个:"The calling thread cannot access this object because a different thread owns it."的InvalidOperationException异常

那么该如何解决这一问题呢?通常的做法是把耗时的函数放在线程池执行,然后切回主线程更新UI显示。前面的updateTime函数改写如下:

private
async
void updateTime()
    {
        while (true)
        {
            await
Task.Run(() => Thread.Sleep(900));
            textBlock.Text = DateTime.Now.ToString();
            await
Task.Delay(100);
        }
    }

这种方式能满足我们的大部分需求。但是,有的操作是比较耗时间的。例如,在多窗口实时监控的时候,我们就需要同时多十来个屏幕每秒钟各进行几十次的刷新,更新图像这个操作必须在UI线程上进行,并且它有非常耗时间,此时又会回到最开始的卡顿的情况。

看起来这个问题无法解决,实际上,WPF只是不允许跨线程访问程序,并非不允许多线程更新界面。我们大可以对每个视频监控窗口单独其一个独立的线程,在那个线程中进行更新操作,此时就不会影响到主线程。MSDN上有篇文章介绍了详细的操作:Multithreaded UI: HostVisual。用这种方式将原来的程序改写如下:

private
void MainWindow_Loaded(object sender, RoutedEventArgs e)
    {
        HostVisual hostVisual = new
HostVisual();

        UIElement content = new
VisualHost(hostVisual);
        this.Content = content;

        Thread thread = new
Thread(new
ThreadStart(() =>
        {
            VisualTarget visualTarget = new
VisualTarget(hostVisual);
            var control = new
UserControl1();
            control.Arrange(new
Rect(new
Point(), content.RenderSize));
            visualTarget.RootVisual = control;

            System.Windows.Threading.Dispatcher.Run();

        }));

        thread.SetApartmentState(ApartmentState.STA);
        thread.IsBackground = true;
        thread.Start();
    }

    public
class
VisualHost : FrameworkElement
    {
        Visual child;

        public VisualHost(Visual child)
        {
            if (child == null)
                throw
new
ArgumentException("child");

            this.child = child;
            AddVisualChild(child);
        }

        protected
override
Visual GetVisualChild(int index)
        {
            return (index == 0) ? child : null;
        }

        protected
override
int VisualChildrenCount
        {
            get { return 1; }
        }
    }

这个里面用来了两个新的类:HostVisual、VisualTarget。以及自己写的一个VisualHost。MSDN上相关的解释,也不算难理解,这里就不多介绍了。最后,再来重构一下代码,把在新线程中创建控件的方式改写如下:

private
void MainWindow_Loaded(object sender, RoutedEventArgs e)
    {
        createChildInNewThread<UserControl1>(this);
    }

    void createChildInNewThread<T>(ContentControl container)
        where
T : UIElement , new()
    {
        HostVisual hostVisual = new
HostVisual();

        UIElement content = new
VisualHost(hostVisual);
        container.Content = content;

        Thread thread = new
Thread(new
ThreadStart(() =>
        {
            VisualTarget visualTarget = new
VisualTarget(hostVisual);

            var control = new
T();
            control.Arrange(new
Rect(new
Point(), content.RenderSize));

            visualTarget.RootVisual = control;
            System.Windows.Threading.Dispatcher.Run();

        }));

        thread.SetApartmentState(ApartmentState.STA);
        thread.IsBackground = true;
        thread.Start();
    }

当然,我这个函数多了一些不必要的的限制:容器必须是ContentControl,子元素必须是UIElement。可以根据实际需要进行相关修改。这里有一个完整的示例,也可以参考一下。

拒绝卡顿——在WPF中使用多线程更新UI的更多相关文章

  1. CleanAOP实战系列--WPF中MVVM自动更新

    CleanAOP实战系列--WPF中MVVM自动更新 作者: 立地 邮箱: jarvin_g@126.com QQ: 511363759 CleanAOP介绍:https://github.com/J ...

  2. 在WPF中减少逻辑与UI元素的耦合

    原文:在WPF中减少逻辑与UI元素的耦合             在WPF中减少逻辑与UI元素的耦合 周银辉 1,    避免在逻辑中引用界面元素,别把后台数据强加给UI  一个糟糕的案例 比如说主界 ...

  3. Android多线程更新UI的方式

    Android下,对于耗时的操作要放到子线程中,要不然会残生ANR,本次我们就来学习一下Android多线程更新UI的方式. 首先我们来认识一下anr: anr:application not rep ...

  4. WPF多线程更新UI的一个解决途径

    那么该如何解决这一问题呢?通常的做法是把耗时的函数放在线程池执行,然后切回主线程更新UI显示.前面的updateTime函数改写如下: private async void updateTime()  ...

  5. 富客户端 wpf, Winform 多线程更新UI控件

    前言 在富客户端的app中,如果在主线程中运行一些长时间的任务,那么应用程序的UI就不能正常相应.因为主线程要负责消息循环,相应鼠标等事件还有展现UI. 因此我们可以开启一个线程来格外处理需要长时间的 ...

  6. 一种WPF在后台线程更新UI界面的简便方法

    WPF框架规定只有UI线程(主线程)可以更新界面,所有其他后台线程无法直接更新界面.幸好,WPF提供的SynchronizationContext类以及C#的Lambda表达式提供了一种方便的解决方法 ...

  7. wpf 绑定数据无法更新ui控件可能存在的问题

    BindingMode的枚举值有: ① OneWay ② TwoWay ③ OneTime:根据源端属性值设置目标属性值,之后的改变会被忽略,除非调用BindingExpression.UpdateT ...

  8. WPF 修改数据后更新UI

    ObservableCollection<T> 只有项添加或删除才会更新UI 要想属性发生变动后立刻更新到UI,必须继承 INotifyPropertyChanged 接口,示例如下 pu ...

  9. 【转】iOS实时卡顿监控

    转自http://www.tanhao.me/code/151113.html/ 在移动设备上开发软件,性能一直是我们最为关心的话题之一,我们作为程序员除了需要努力提高代码质量之外,及时发现和监控软件 ...

随机推荐

  1. ArrayList和Array之间的转换

    ArrayList转Array (1):使用ArrayList的toArray方法.     1)当ArrayList中存放的是引用类型时(例如String),成功     /**      * 使用 ...

  2. jQuery学习-----(一)JQuery的'$'符号用法

    1.jQuery的三种$() 1)$()可以是$(expresion),即css选择器.Xpath或html元素,也就是通过上述表达式来匹配目标元素.   比如:$("a")构造的 ...

  3. VB.NET转C#代码的工具

    比如VB.NET的代码: For Each prop In entity.Details.Properties.All(). OfType(Of Microsoft.LightSwitch.Detai ...

  4. 基于css3新属性transform及原生js实现鼠标拖动3d立方体旋转

    基于css3新属性transform,实现3d立方体的旋转 通过原生JS,点击事件,鼠标按下.鼠标抬起和鼠标移动事件,实现3d立方体的拖动旋转,并将旋转角度实时的反应至界面上显示 实现原理:通过获取鼠 ...

  5. linux实现nginx按照日期存储日志

    通过shell脚本实现+定时任务+nginx信号管理实现日志按日期存储. 1.编写shell脚本,实现日志按日期存储 #!/bin/bash base_path='/home/wwwlogs/' lo ...

  6. CCNP第二天 帧中继综合实验

    实验题如图所示: 要求全网可达 R5为帧中继交换机 R6 和 R1之间为快速以太网接口 所使用的拓扑为CCNA标准版拓扑图,如下所示: -------------------------------- ...

  7. 小课堂Week8 例外处理设计的逆袭Part1

    小课堂Week8 例外处理设计的逆袭Part1 今天和大家讲一本书,书名是<例外处理设计的逆袭>. 为什么想讲这本书,是因为,例外处理在程序代码中到处存在,但是这些到底该如何写好,总觉得有 ...

  8. c语言随机数

    写得我自己都看不好了:大家都比较喜欢吃快餐,只需要尽快告诉读者怎么用起来就行了.不想听啰啰嗦嗦说一堆,然后例程还特别麻烦 so:  1.基本 int seed = time(0);//#include ...

  9. JavaScript console 用法大全

    对于前端开发者来说,在开发过程中需要监控某些表达式或变量的值的时候,用 debugger 会显得过于笨重,取而代之则是会将值输出到控制台上方便调试.最常用的语句就是console.log(expres ...

  10. EXTJS 3.0 资料 控件之 GridPanel属性与方法大全

    1.Ext.grid.GridPanel 主要配置项: store:表格的数据集 columns:表格列模式的配置数组,可自动创建ColumnModel列模式 autoExpandColumn:自动充 ...