在WPF中怎么使用多线程更新UI
本篇内容主要讲解“在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(); }
到此,相信大家对“在WPF中怎么使用多线程更新UI”有了更深的了解,不妨来实际操作一番吧!这里是编程网网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!
免责声明:
① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。
② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341