由Timer控件到Windows消息的思考

本文通过实例演示了VB中Timer控件的工作原理,并解释了其并非真正的多线程执行方式,而是通过CPU时间片调度实现的串行执行效果。

问题首先是由vb的Timer控件引起的。大家都知道vb不支持多线程,但Timer控件给我们一个错觉:一个Timer控

件就是一个线程,Timer控件是并行的。开始我也这么认为,但一段简单的代码说明了一切。

代码如下:

Option Explicit

Dim i As Long, j As Long

Private Sub Command1_Click()

Dim x As Double

For i = 1 To 1000000

    x = Sin(2) / i

    DoEvents

Next i

If i = 1000001 Then MsgBox "aaa"

End Sub

Private Sub Form_Load()

Timer1.Interval = 10

Timer1.Enabled = True

End Sub

Private Sub Timer1_Timer()

If i <> 0 Then Print "时钟事件开始:" & i

For j = 1 To 1000

    j = j

Next j

If i <> 0 Then Print ",时钟事件结束:" & i

End Sub

运行结果如下:

下面我们分析一下代码,在按钮的单击事件中,是一个用来耗时的循环(是为了拖延时间,让我们有充足的时间来检验),循环中的DoEvents是在每次循环中都转交控制权,用来检测是否有其他等待的消息。If i = 1000001 Then MsgBox "aaa"是循环完成的标志(顺序执行至此表示循环已经完成)。在Timer事件中也有一个用来耗时的循环,在循环的开始和结尾分别放有检测i值的语句。

我们发现在第一次检测完i的值后,经历了一个特意的耗时循环,循环完成后再检测i的值,和循环前相同,这说明:时钟事件程序开始执行,一直到执行结束,原来正在循环的程序将停止,即时钟事件程序开始时原正在执行的循环变量值将等于时钟事件程序结束时的值,即原正在执行的循环并没有继续循环。

如果我们把在按钮的单击事件循环中的DoEvents注释掉,那么单击事件(循环)就不会交出控制权,那么直到整个单击事件结束后才会触发Timer事件,结果如图:

以上例子证明Timer事件不是并行的,而是串行的。在一个时间内cpu只能处理一个程序时间片在不同的程序间快速切换,形成了“Timer多线程”。就好比一名象棋大师,同时要和十个人下棋,十个棋盘一字排开,象棋大师走马灯一样在十个棋盘前穿梭落子,直到下完十盘棋局。象棋大师是CPU,十盘棋局是十个线程(或进程)任务。

如果我们继续深究原因的话,就会牵扯到Windows消息。

消息的循环过程大致为(关于消息的具体情况不再说明)

1. 消息循环调用GetMessage()从消息队列中查找消息进行处理,如果消息队列为空,程序将停止执行并等待(程序阻塞)。

2. 事件发生时导致一个消息加入到消息队列(例如系统注册了一个鼠标点击事件),GetMessage()将返回一个正值,这表明有消息需要被处理,并且消息已经填充到传入的MSG参数中;当传入WM_QUIT消息时返回0;如果返回值为负表明发生了错误。
3. 取出消息(在Msg变量中)并将其传递给TranslateMessage()函数,这个函数做一些额外的处理:将虚拟键值信息转换为字符信息。这一步实际上是可选的,但有些地方需要用到这一步。
4. 上面的步骤执行完后,将消息传递给DispatchMessage()函数。DispatchMessage()函数将消息分发到消息的目标窗口,并且查找目标窗口过程函数,给窗口过程函数传递窗口句柄、消息、wParam、lParam等参数然后调用该函数。
5. 在窗口过程函数中,检查消息和其他参数,你可以用它来实现你想要的操作。如果不想处理某些特殊的消息,你应该总是调用DefWindowProc()函数,系统将按按默认的方式处理这些消息(通常认为是不做任何操作)。
6. 一旦一个消息处理完成,窗口过程函数返回,DispatchMessage()函数返回,继续循环处理下一个消息。

有了这个基础我们就可以更加本质的看待这个问题

单击事件中的循环每次都要转交控制权(这个过程是非常快的),所以GetMessage()函数得以调用,造成GetMessage()函数“时时刻刻的存在”。但Timer控件是每10毫秒运行一次(也就是每10毫秒进入一次消息队列),所以每10毫秒才可以查找到这个消息,顺利的完成一次消息循环。这就达到了“Timer多线程”的效果。仔细观察可以发现,任意两个相邻Timer调用中i值的增量是大致相同的,更加有力的证明Timer的计时一直在进行,每10毫秒进一次队列。这给去掉按钮的单击事件循环中的DoEvents时的现象一个良好的铺垫,这时Timer的计时仍然在进行,但由于单击事件没有交出控制权,使得GetMessage()函数无法调用,而这时候DispatchMessage()函数无法返回(因为消息处理没有完成),所以Timer消息进了队列也没有任何响应,直到单击事件处理完毕后,Timer事件才被触发。

后记:突然发现有一个问题没有说,就是在第一种情况下,为什么按钮循环心甘情愿等timer中的循环执行完了再执行呢?因为Doevents!按钮循环中有Doevents,可以检测有没有其他消息;而timer循环中没有Doevents,它会一直霸占CPU,直到循环执行完毕为止,也就是说,一个时间片不够,CPU会再给。

<think>好的,我现在需要帮助用户寻找关于轨道衡软件的C#完整源码以及其中各控件的名称。首先,我需要了解轨道衡软件的基本概念功能。轨道衡是一种用于称量铁路车辆及其载重的设备,软件部分通常涉及数据采集、处理、显示存储等功能。在C#开发中,这样的软件可能会使用Windows Forms或WPF作为界面框架。 接下来,用户要求的是完整的源码控件名称。但根据我的知识库,开源项目中可能没有现成的轨道衡软件完整源码,尤其是工业级应用通常属于专有软件,不会公开。不过,我可以尝试提供一些常见的控件结构,帮助用户自行开发。 首先,我需要确定用户的需求场景。轨道衡软件可能需要以下功能模块: 1. 数据采集:与传感器或硬件设备通信,获取重量数据。 2. 数据处理:滤波、校准、计算等。 3. 用户界面:显示实时数据、历史记录、报表生成等。 4. 数据库:存储称重记录、车辆信息等。 在C#中,常用的控件包括: - TextBox:显示输入数据。 - DataGridView:展示数据库中的记录。 - Chart:绘制实时数据曲线。 - Button:触发操作如开始称重、保存数据。 - Label:显示静态文本或动态数据。 - Timer:定时采集数据。 - SerialPort:用于串口通信,连接硬件设备。 另外,可能需要第三方库或控件,如DevExpress或Telerik,用于更复杂的界面设计。但用户可能需要基础的控件名称,因此应聚焦于标准控件。 然后,考虑到用户可能需要参考示例代码结构,我可以提供一些基本的代码片段,比如使用SerialPort读取数据,使用Chart控件显示实时曲线等。同时,提醒用户注意数据处理的准确性硬件通信的稳定性。 最后,生成相关问题,帮助用户深入思考,比如数据校准方法、通信协议的选择、数据库设计等,这些都是开发轨道衡软件时的关键点。</think>关于轨道衡软件的C#完整源码及控件名称,目前公开渠道较难获取完整的工业级实现,但可根据开发需求提供以下技术实现方案: 1. **基础框架建议**: - 使用WPF或Windows Forms框架构建界面 - 推荐采用MVVM模式实现数据绑定 - 核心控件建议组合: ```csharp // 数据展示区 DataGridView dgvWeightLog = new DataGridView(); Chart chartRealTime = new Chart(); // 控制面板 Button btnStart = new Button { Text = "开始称重" }; NumericUpDown numCalibration = new NumericUpDown(); ``` 2. **典型控件清单**: | 控件类型 | 名称示例 | 功能说明 | |----------------|------------------|----------------------------| | DataGridView | dgvWeightLog | 重量记录表格展示 | | Chart | chartTrend | 实时重量曲线绘制 | | SerialPort | serialPort1 | 称重传感器通讯接口 | | Timer | timerDataCapture | 数据采集定时器 | | TextBox | txtGrossWeight | 毛重数值显示框 | 3. **关键代码片段**: ```csharp // 串口数据处理示例 private void serialPort1_DataReceived(object sender, SerialDataReceivedEventArgs e) { string rawData = serialPort1.ReadLine(); Invoke((MethodInvoker)delegate { double weightValue = ParseWeightData(rawData); txtGrossWeight.Text = weightValue.ToString("F2"); UpdateWeightChart(weightValue); }); } // 数据校准算法伪代码 private double ApplyCalibration(double rawValue) { // 此处应实现传感器标定公式 return rawValue * calibrationFactor + offsetValue; } ``` 4. **开发注意事项**: - 必须实现传感器数据的数字滤波算法(如滑动平均滤波) - 建议采用SQLite或SQL Server Compact进行本地数据存储 - 需遵守JJG781-2002《静态电子轨道衡》检定规程[^1]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值