一、WPF的线程
对于初学wpf的人来说,一般会把所有的程序都在一个线程中运行,当数据量较大,需要频繁刷新界面时,界面会出现卡顿的情况。
1、当我们打开一个WPF应用程序即开启了一个进程,该进程中都会加载两个重要的线程:一个用于呈现用户界面,另一个用于管理用户界面。呈现线程是一个在后台运行的隐藏线程,因此您通常面对的唯一线程 就是 UI线程。WPF 要求将其大多数对象与 UI 线程进行关联,这称之为
线程关联
,意味着要使用一个 WPF 对象,
只能在创建它的线程上使用,
在其他线程上使用它会导致引发运行时异常。
-
一个线程用于处理呈现:隐藏在后台运行
-
一个线程用于管理用户界面:接收输入、处理事件、绘制屏幕以及运行应用程序代码,即UI线程。
-
不管是WinForm应用程序还是WPF应用程序,实际上都是一个进程,一个进程可以包含多个线程,其中有一个是主线程,其余的是子线程。
2、在 WPF 中绝大部分控件都继承自 DispatcherObject,甚至包括 Application。这些继承自 DispatcherObject 的对象具有线程关联特征,也就意味着只有创建这些对象实例,且包含了 Dispatcher 的线程(通常指默认 UI 线程)才能直接对其进行更新操作。
在 WPF 中,DispatcherObject 只能通过与它关联的 Dispatcher 进行访问。 例如,后台线程不能更新由 UI 线程创建的 Label的内容。
二、 Dispatcher类
1、在UI线程中有一个Dispatcher对象,管理每一个需要执行的工作项。Dispatcher会根据每个工作项的优先级排队。向Dispatcher列队中添加工作项时可指定10个不同的级别。那么问题来了,如果遇到耗时操作的时候,该操作如果依旧发生在UI线程中,Dispatcher 列队中其他的需要执行的工作项都要等待,从而造成界面假死的现象。为了加快响应速度,提高用户体验,我们应该尽量保证Dispatcher 列队中工作项要
小
。所以,对于耗时操作,我们应该开辟一个新的子线程去处理,在操作完成后,通过向UI线程的Dispatcher列队注册工作项,来通知UI线程更新结果。
Dispatcher类详细介绍
2、Dispatcher提供两个注册工作项的方法:Invoke 和 BeginInvoke。
这两个方法均调度一个委托来执行。Invoke 是同步调用,也就是说,直到 UI 线程实际执行完该委托它才返回。BeginInvoke是异步的,将立即返回。
-
Dispatcher实际上并不是多线程
-
子线程不能直接修改UI线程,必须通过向UI线程中的Dispatcher注册工作项来完成
-
Dispatcher 是单例模式,暴露了一个静态的CurrentDispatcher方法用于获得当前线程的Dispatcher
-
每一个UI线程都至少有一个Dispatcher,一个Dispatcher只能在一个线程中执行工作。
3、UI线程中创建的对象,如何在非UI线程中更新
参考:
https://www.cnblogs.com/chillsrc/p/4482691.html
(1)前台,创建对象
<Window x:Class="WpfApp1.WindowThd"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="WindowThd" Height="300" Width="400">
<StackPanel>
<Label x:Name="lblHello">欢迎你光临WPF的世界!</Label>
<Button Name="btnThd" Click="btnThd_Click" >多线程同步调用</Button>
<Button Name="btnAppBeginInvoke" Click="btnAppBeginInvoke_Click" >BeginInvoke 异步调用</Button>
</StackPanel>
</Grid>
</Window>
(2)后台创建非UI线程,若直接更新UI线程的对象就会报错
namespace WpfApp1
/// <summary>
/// WindowThd.xaml 的交互逻辑
/// </summary>
public partial class WindowThd : Window
public WindowThd()
InitializeComponent();
//更新UI线程创建的对象
private void ModifyUI()
// 模拟一些工作正在进行
Thread.Sleep(TimeSpan.FromSeconds(2));
lblHello.Content = "欢迎你光临WPF的世界,Dispatcher";
//开启非UI线程
private void btnThd_Click(object sender, RoutedEventArgs e)
Thread thread = new Thread(ModifyUI);
thread.Start();
程序报错:
正确方法:
修改上面的ModifyUI()函数
方法一:使用Invoke
private void ModifyUI()
// 模拟一些工作正在进行
Thread.Sleep(TimeSpan.FromSeconds(2));
//lblHello.Content = "欢迎你光临WPF的世界,Dispatcher";
this.Dispatcher.Invoke(DispatcherPriority.Normal, (ThreadStart)delegate()
lblHello.Content = "欢迎你光临WPF的世界,Dispatche 同步方法 !!";
方法二:使用 BeginInvoke
private void btnAppBeginInvoke_Click(object sender, RoutedEventArgs e)
new Thread(() =>
Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal,
new Action(() =>
Thread.Sleep(TimeSpan.FromSeconds(2));
this.lblHello.Content = "欢迎你光临WPF的世界,Dispatche 异步方法!!"+ DateTime.Now.ToString();
}).Start();
三、实验测试
1、简单测试
做了一个上位机,接收光谱仪的数据,并将线阵CMOS的数据绘制成图,一直存在界面卡顿的问题。
ShowCCD()函数用来将串口接收的数据绘制成图。在串口接收处理事件中,接收完一帧数据后再调用ShowCCD()函数绘图。
直接调用:
ShowCCD();
测试效果:
整个界面非常卡顿,CPU占到了61%。
方法二:使用Dispatcher.Invoke
this.Dispatcher.Invoke(ShowCCD);
测试效果:
页面相对较为卡顿,CPU占用42%.
使用Dispatcher.BeginInvoke
this.Dispatcher.BeginInvoke((Action)delegate(){ShowCCD();});
出现报错。
降低发送的帧数后,可以显示。
通过以上分析程序卡顿的原因的我在UI线程上接收数据,并使用Dispatcher类显示数据,实际上还是运行在UI线程上,造成卡顿。
四、优化方案
1、原来的主窗口还是用来进行数据的接收及数据的运算处理,即数据处理过程还是运行在UI线程。
2、新建一个wpf窗口,这个窗口使用zedgraph控件出图,开启一个Timer定时器线程定时刷新界面,定时20ms,即刷新频率为50HZ,若刷新的速率和接收数据的速率设置为一样快,我认为也没有必要,太快的刷新速率人眼也分辨不出来。
WPF下使用ZedGraph控件
private void Window_Loaded(object sender, RoutedEventArgs e)
// savepicture_th = new Thread(new ThreadStart(savepicture));
// savepicture_th.Start(); //启动线程
// savepicture();
//这种定时器不是工作在UI线程
System.Timers.Timer timer = new System.Timers.Timer(20);//实例化Timer类,设置间隔时间为20毫秒;
timer.Elapsed += new System.Timers.ElapsedEventHandler(theout); //注册中断事件
timer.Start();//启动定时器
//时间中断事件 以50Hz的频率刷新界面
public void theout(object source, System.Timers.ElapsedEventArgs e)
showChart1(MainWindow.aveccdData0);
PointPairList list2 = new PointPairList();
private void showChart1(double[] value)
list2.Clear();//清空数组
zedGraphControl.GraphPane.Title.Text = "";
zedGraphControl.GraphPane.XAxis.Title.Text = "";
zedGraphControl.GraphPane.YAxis.Title.Text = "";
zedGraphControl.GraphPane.XAxis.Scale.Min = 0; //X轴最小值0
//zedGraphControl1.GraphPane.XAxis.Scale.MaxAuto = true; //X轴最大30
zedGraphControl.GraphPane.XAxis.Scale.Max = 520;
zedGraphControl.GraphPane.XAxis.Scale.MinorStep = 10;
zedGraphControl.GraphPane.XAxis.Scale.MajorStep = 100;
zedGraphControl.GraphPane.YAxis.Scale.Min = 0;
zedGraphControl.GraphPane.YAxis.Scale.Max = 2000;
zedGraphControl.GraphPane.YAxis.Scale.MinorStep = 20;
zedGraphControl.GraphPane.YAxis.Scale.MajorStep = 500;
zedGraphControl.AxisChange();
for (int j = 0; j < value.Length; j++)
list2.Add(j, value[j]);
this.Dispatcher.Invoke(RefreshInterface);//切换到UI线程更新界面
//刷新界面
private void RefreshInterface()
zedGraphControl.GraphPane.CurveList.Clear();
zedGraphControl.GraphPane.AddCurve("", list2, System.Drawing.Color.Red, SymbolType.None);//绘制图表
// zedGraphControl.AxisChange();//刷新界面
zedGraphControl.Refresh();
Timer定时器线程里刷新界面时,必须使用Dispatcher类,若在Timer定时器线程里直接刷新界面就会报错,这违反了WPF下用其他线程刷新界面的规则。
如果使用定时器DispatcherTimer则不需要使用Dispatcher类,因为DispatcherTimer就是在UI线程中,仅是猜测未经实验。
测试效果:界面完全不卡顿,CPU占用率10%。
Windows系统是一个多线程的操作系统,进程(Process)是Windows系统中的一个基本概念,它包含着一个运行程序所需要的资源。一个正在运行的应用程序在操作系统中被视为一个进程,进程可以包括一个或多个线程。线程是操作系统分配处理器时间的基本单元,在进程中可以有多个线程同时执行代码。进程之间是相对独立的,一个进程无法访问另一个进程的数据(除非利用分布式计算方式),一个进程运行的失败也不会影响其他进程的运行,Windows系统就是利用进程把工作划分为多个独立的区域的。进程可以理解为一个程序的基本边界。
通过使用C++ + Opencv 编写算法,然后用WPF(C#)编写程序界面,实现交互
可以参考MSDN文档:https://docs.microsoft.com/en-us/cpp/windows/pin-ptr-cpp-cli?view=vs-2017
本文将用一个进度条控件作为例子来介绍WPF的多线程,进度条的例子可以较全面的让我们认识WPF多线程的特点。
当用户在我们的应用程序下载东西或者加载大量数据的时候,不可避免需要用户等待一段较长的时间,这时候,我们需要一个进度条来实时反映进度给用户,以免用户以为程序死机从而进行一系列奇怪的操作。
那么,先让我们上一个进度条吧。
一、线程概述:【引用MSDN】
通常,WPF 应用程序从两个线程开始:一个用于处理呈现,一个用于管理 UI。呈现线程有效地隐藏在后台运行,而 UI 线程则接收输入、处理事件、绘制屏幕以及运行应用程序代码。大多数应用程序都使用一个 UI 线程,但在某些情况下,最好使用多个线程。我们将在后面举例说明这一点。
UI 线程对一个名为 Dispatcher 的对象内的工作项进行排队。Dispatc
1.线程是一个可执行的路径,它可以独立于其他线程执行。2.每个线程都在操作系统的进程内执行,而操作系统进程提供了程序运行的独立环境。3.单线程应用,在进程的独立环境里只跑一个线程,所以该线程拥有独占权。4.多线程应用,单个进程中会跑多个线程,他们会共享当前的执行环境(内存)等。5.进程和线程的对应关系,一个进程可以拥有多个线程,多个线程只能属于一个进程。例如:一个非常耗时的操作(读数据库、复杂耗时的计算),如果只用主线程执行UI线程会“假死”专业术语叫线程阻塞。
线程几乎是所有项目或多或少都在用的一种技术手段了,在WPF项目中更是用的只会多不会少的。所以通过WPF的世界来介绍下线程。学习过《计算机操作系统》的基本很容易去理解线程和同/异步有多么的重要,进程、线程和同/异步的出现是让我们的计算机有这么大发展的基础之一。WPF的多线程和window窗体中的多线程基本一样,只是实现的细节和底层支持有点不一样。
我们的程序通常会执行很多的任务,在我们没处理的正常...
《WPF编程宝典》一个多线程示例,记录下。一、介绍:通过多线程特性可使WPF应用程序执行后台工作,同时保持用户界面能够进行响应。1 了解多线程模型 WPF元素具有线程关联性:创建WPF元素的线程拥有所有所创建的元素,其他线程不能直接与这些WPF元素进行交互 dispatcher(调度程序)拥有应用程序线程,并管理工作项队列。当新线程第...