拒绝卡顿——在WPF中使用多线程更新UI
原文:拒绝卡顿——在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。可以根据实际需要进行相关修改。这里有一个完整的示例,也可以参考一下。

posted on 2018-05-15 08:30 NET未来之路 阅读(...) 评论(...) 编辑 收藏

转载于:https://www.cnblogs.com/lonelyxmas/p/9039095.html

拒绝卡顿——在WPF中使用多线程更新UI相关推荐

  1. 现在梦三国2服务器不稳定,《梦三国2》拒绝卡顿 从自我电脑优化开始

    原标题:<梦三国2>拒绝卡顿 从自我电脑优化开始 游戏卡顿一直是玩竞技游戏时头疼的问题.游戏卡顿也是战力匹配上分时的噩梦.尤其是每天晚上双倍战力时间段,出现卡顿的几率会更高.我们都知道梦三 ...

  2. 高性能游戏本搭服务器,高性能游戏本盘点,玩游戏拒绝卡顿!

    原标题:高性能游戏本盘点,玩游戏拒绝卡顿! 2018年结束之后,很多人都想购买一台游戏本玩游戏,算是犒劳一下自己,也算是努力一整年来给自己送一份好礼物吧.而在上年,各厂商都发布了不少游戏本新品,今天就 ...

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

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

  4. 为什么子线程中不能直接更新UI

    点击上方"dotNET全栈开发","设为星标" 加"星标★",每天11.50,好文必达 全文约4000字,预计阅读时间8分钟 当初有同事就碰 ...

  5. android 异步刷新 方法,android应用中实现异步更新UI的方法有哪些

    android应用中实现异步更新UI的方法有哪些 发布时间:2020-12-07 17:12:00 来源:亿速云 阅读:144 作者:Leah android应用中实现异步更新UI的方法有哪些?相信很 ...

  6. P110 课时111.多线程更新UI数据

    import sys import time from PyQt5.QtCore import QThread, pyqtSignal, QDateTime from PyQt5.QtWidgets ...

  7. win10系统文件拖拽卡顿_win10电脑中鼠标拖动窗口有延迟如何解决

    在win10系统中,有时候会使用鼠标来拖动窗口操作文件,然而有时候会遇到鼠标拖动窗口有延迟.卡顿的情况,这样操作就很不方便了,接下来就为大家讲解一下win10电脑中鼠标拖动窗口有延迟的具体解决步骤. ...

  8. LG G3升级Android 6.0 Marshmallow方法亲测!界面渲染精美,拒绝卡顿,提升续航!

    14年入手的国行LG G3 d857机子,一直很喜欢LG的knock code和简洁的流线型外观,周身无按键的和背部按键的设计确实提升了操作体验.去年LG  OTA推送了Android 5.0 Lol ...

  9. Java线程更新ui_android使用多线程更新ui示例分享

    Android线程涉及的技术有:Handler;Message;MessageQueue;Looper;HandlerThread. 下面看一段在线程中更新UI的代码: public class Ma ...

  10. iphone储存空间系统怎么清理_教你快速清理 iPhone 系统缓存垃圾,拒绝卡顿!

    「 改变能改变的一切,接受不能改变的一切!」 ▼ "iPhone存储空间"里的系统为何占用了几十G的甚至上百G的内存,如何清理?当我们使用 iPhone 一段时间之后,系统或应用中 ...

最新文章

  1. Swift学习 OOP三大特性:继承、多态、封装
  2. php java sql_java如何连接sql数据库?
  3. 2020年丘赛放榜:北大斩获5金11银强势霸榜
  4. 笔记-项目管理基础知识-项目信息(工作绩效信息、绩效数据、绩效报告)
  5. 每天一道LeetCode-----计算二叉树的最大深度及最小深度,判断二叉树是否是高度平衡二叉树
  6. mysql表连接_mysql表连接
  7. Java,JavaFX的流利设计风格文本字段和密码字段
  8. 苹果CMSv10官方版程序包
  9. python盒中取球_在Python中找到占据给定球的盒子的位置
  10. collins词典第八版mdx_英语词典选择
  11. plsql快捷执行方式_UG编程必备的快捷键,收藏转发!
  12. 测绘的真正出路在于什么?
  13. 为什么MASKRCNN中使用ROIAlign替代ROIPool
  14. 山寨手机拇指30掌盟APP软件游戏下载安装破解激活图文教程2
  15. 历史类:古希腊与亚历山大帝国
  16. 【Unity2D入门教程】简单制作一个弹珠游戏之制作场景①(开场,结束,板子,球)
  17. 大文件传输软件的优势你了解吗?
  18. ndnSIM学习(十)——apps之ndn-producer.cpp和ndn-consumer.cpp源码分析
  19. c语言中\n,\t,\r,\b的用法和区别
  20. 杰理之AD140 开发板芯片没 trim 过【篇】

热门文章

  1. 地温梯度 河南_河南省地热(温泉)分布规律
  2. 基于京东家电商品知识图谱的自动问答系统(三) -- Java实现问答系统
  3. fidder不拦截_利用Fiddler拦截接口请求并篡改数据
  4. mysql学习资料_PHP程序员2020学习方向:高并发、性能调优、分布式、微服务...
  5. 数据库出现'\xF0\x9F\x98\xB8'
  6. Apache静态缓存配置
  7. JavaScript数组forEach循环
  8. avalon2学习教程14动画使用
  9. RMAN-20201: datafile not found in the recovery catalog
  10. emacs工程管理,cedet ede插件自动构建Make,Automake