您的位置:首页 > 产品设计 > UI/UE

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

2017-02-22 18:19 471 查看

http://www.cnblogs.com/TianFang/p/3969430.html

补充:http://blog.higan.me/dot-net-thread-dispatch-model-multi-thread-control/?utm_source=tuicool&utm_medium=referral

有经验的程序员们都知道:不能在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。用这种方式将原来的程序改写如下:
 e)

    {

        ();

        (hostVisual);

        .Content = content;

        (() =>

        {

            (hostVisual);

            ();

            control.Arrange((), content.RenderSize));

            visualTarget.RootVisual = control;

            System.Windows.Threading..Run();

        }));

        thread.SetApartmentState(.STA);

        thread.IsBackground = ;

        thread.Start();

    }

    

    {

         child;

         child)

        {

            )

                );

            .child = child;

            AddVisualChild(child);

        }

         index)

        {

            ;

        }

         VisualChildrenCount

        {

             1; }

        }

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

    {

        createChildInNewThread<);

    }

     container) 

        ()

    {

        ();

        (hostVisual);

        container.Content = content;

        (() =>

        {

            (hostVisual);

            ();

            control.Arrange((), content.RenderSize));

            visualTarget.RootVisual = control;

            System.Windows.Threading..Run();

        }));

        thread.SetApartmentState(.STA);

        thread.IsBackground = ;

        thread.Start();

    }
当然,我这个函数多了一些不必要的的限制:容器必须是ContentControl,子元素必须是UIElement。可以根据实际需要进行相关修改。这里有一个完整的示例,也可以参考一下。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: