尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

结合代码(ControlMonitor 和 ControlHardWareStaus),我们将深入探讨 Application.DoEvents() 的意义及其潜在问题,以及为什么使用 Invoke

结合代码(ControlMonitor 和 ControlHardWareStaus),我们将深入探讨 Application.DoEvents() 的意义及其潜在问题,以及为什么使用 Invoke 结合代码ControlMonitor 和 ControlHardWareStaus我们将深入探讨 Application.DoEvents() 的意义及其潜在问题以及为什么使用 Invoke 替代 BeginInvoke 可以解决状态不一致问题例如“UNCONNECTED”文本与 scralballpane资源_54.png 背景不匹配。1. Application.DoEvents() 的意义1.1 什么是 Application.DoEvents()Application.DoEvents() 是 Windows Forms 提供的一个静态方法用于强制 UI 线程处理当前消息队列中的所有待处理消息Windows 消息如 WM_PAINT、WM_MOUSEMOVE 等。它通常在以下场景中使用处理 UI 更新当程序执行长时间运行的任务如循环或复杂计算时UI 线程可能被阻塞导致界面无响应例如窗口卡顿、控件不刷新。调用 Application.DoEvents() 允许 UI 线程处理消息队列中的消息确保界面保持响应。模拟多线程行为在单线程环境中Application.DoEvents() 可以让程序在执行耗时操作时“暂停”主逻辑处理 UI 事件。在你的代码中Application.DoEvents() 出现在 _MonitorHardWareStatus 方法的 while 循环中csharpprivate void _MonitorHardWareStatus() { while (Monitor) { UpdataAUXStatus(); UpdataChamberStatus(); UpdataPowerStatus(); UpdataChannelStatus(); UpdataBIBStatus(); UpdataHeatBoardStatus(); Application.DoEvents(); Thread.Sleep(1000); } }1.2 Application.DoEvents() 在你的代码中的作用在 _MonitorHardWareStatus 中Application.DoEvents() 的作用是强制 UI 更新后台线程通过 BeginInvoke 将 UI 更新如设置 ControlHardWareStaus.Content 和 HardwareConnectStatus放入 UI 线程的消息队列。Application.DoEvents() 强制 UI 线程处理这些消息确保控件如 labelContent 和 BackgroundImage及时刷新。避免 UI 卡顿由于 _MonitorHardWareStatus 运行在后台线程Application.DoEvents() 确保 UI 线程在每次轮询循环中处理用户交互如点击 labelContent和重绘请求保持界面响应。1.3 Application.DoEvents() 的问题尽管 Application.DoEvents() 看似解决了 UI 响应问题但它在你的场景中引入了以下问题状态不一致Application.DoEvents() 强制 UI 线程处理消息队列中的所有消息包括 BeginInvoke 触发的 Content 和 HardwareConnectStatus 更新。由于 BeginInvoke 是异步调用消息队列可能包含多个更新委托例如先设置 Content UNCONNECTED后设置 HardwareConnectStatus InUsing。如果硬件状态快速变化消息队列中的更新顺序可能导致 UI 显示不一致例如“UNCONNECTED”文本与 scralballpane资源_54.png 背景不匹配。示例后台线程在 UpdataBIBStatus 中调用 BeginInvoke 设置 Content UNCONNECTED。在下一次轮询前硬件状态变为 Tc.BIBStatus 1触发 BeginInvoke 设置 HardwareConnectStatus InUsing。Application.DoEvents() 可能先处理 HardwareConnectStatus 的更新显示 scralballpane资源_54.png后处理 Content 的更新显示“UNCONNECTED”导致不一致。不可预测的行为Application.DoEvents() 允许 UI 线程处理任何消息包括用户交互如点击 labelContent或窗口调整。这可能导致在不恰当的时机执行事件处理器干扰后台线程的逻辑。例如用户在 Application.DoEvents() 执行期间点击 labelContent可能触发 labelContent_Click 事件访问共享资源如 HardwareMgr.ErrorCodeMap导致潜在的线程安全问题。性能开销Application.DoEvents() 强制处理整个消息队列即使某些消息无需立即处理增加 UI 线程负担。在你的场景中每秒调用一次 Application.DoEvents()可能导致频繁的 UI 重绘降低性能。复杂调试Application.DoEvents() 改变了程序的执行流难以预测哪些消息被处理增加了调试状态不一致问题的难度。1.4 为什么不推荐使用 Application.DoEvents()线程安全问题Application.DoEvents() 在后台线程中调用可能导致 UI 线程在不安全的上下文中处理消息引发数据竞争或异常。替代方案更优现代异步编程模型如 async/await 或事件驱动通过任务和事件循环管理异步操作无需 Application.DoEvents()。设计缺陷使用 Application.DoEvents() 通常是设计不良的标志表明程序没有正确分离 UI 和后台逻辑。在你的场景中Application.DoEvents() 是状态不一致问题的一个间接原因因为它加剧了 BeginInvoke 异步更新的不可控性。移除它并采用更现代的异步模型可以解决问题。2. 使用 Invoke 替代 BeginInvoke2.1 Invoke 和 BeginInvoke 的区别Control.Invoke同步调用将委托回调发送到 UI 线程的消息队列并阻塞调用线程直到 UI 线程执行完成。特点确保委托执行后UI 控件如 labelContent.Text立即更新。适合需要保证更新顺序的场景。可能阻塞调用线程例如后台线程但在你的场景中每 100ms 或更低频率更新性能影响可忽略。使用场景需要确保 UI 更新是原子性和顺序正确的场景。Control.BeginInvoke异步调用将委托放入 UI 线程的消息队列立即返回调用线程不等待 UI 线程执行。特点非阻塞适合高吞吐量场景。消息队列可能导致更新顺序不可控尤其在快速连续调用时。使用场景需要快速提交大量更新且不关心立即完成。在你的代码中ControlHardWareStaus 的 Content、Status 和 HardwareConnectStatus 属性使用 BeginInvoke 更新 UIcsharppublic string Content { get m_Content; set { string old m_Content; bool changed old ! value; if (!changed) { return; } m_Content value; if (!string.IsNullOrEmpty(value)) { if (InvokeRequired) { MethodInvoker me delegate () { labelContent.Text value; }; BeginInvoke(me, null); } else { labelContent.Text value; } } } }2.2 BeginInvoke 在你的代码中的问题BeginInvoke 导致状态不一致“UNCONNECTED”与 scralballpane资源_54.png 不匹配的原因消息队列乱序UpdataBIBStatus 中Content 和 HardwareConnectStatus 的更新分别调用 BeginInvoke生成两个独立的委托。例如Content UNCONNECTED 触发一个 BeginInvoke。HardwareConnectStatus InUsing 触发另一个 BeginInvoke。UI 线程的消息循环按顺序处理这些委托但如果硬件状态快速变化例如 Tc.BIBStatus 从 0 到 1消息队列可能包含多个更新委托导致 HardwareConnectStatus InUsingscralballpane资源_54.png先执行而 Content UNCONNECTED 后执行造成不一致。快速状态变化后台线程每秒轮询可能捕获硬件状态的中间状态例如 Tc.BIBStatus 0然后快速变为新状态Tc.BIBStatus 1。BeginInvoke 的异步性无法保证这些更新按逻辑顺序执行。与 Application.DoEvents() 的交互Application.DoEvents() 强制 UI 线程处理消息队列可能在后台线程下一次轮询前处理部分 BeginInvoke 委托导致更新不完整。2.3 为什么用 Invoke 替代 BeginInvoke使用 Invoke 可以解决状态不一致问题原因如下同步更新Invoke 确保 Content 和 HardwareConnectStatus 的更新在 UI 线程上立即完成避免消息队列中的顺序问题。例如在 UpdataBIBStatus 中将两个更新合并到一个 Invoke 委托中csharpif (control.InvokeRequired) { control.Invoke((MethodInvoker)delegate { control.HardwareConnectStatus channelStatus; control.Content bibStr; }); }这样Content 和 BackgroundImage 的更新是原子性的不会被其他消息打断。简化消息队列Invoke 减少消息队列中的委托数量一个 Invoke 包含所有更新降低事件循环的负担。保证一致性在你的场景中硬件状态更新频率较低每 100ms 或更低Invoke 的阻塞开销可以忽略且确保 UI 显示始终与硬件状态一致。调试简单Invoke 的同步性使更新顺序可预测便于调试状态不一致问题。2.4 性能考虑阻塞开销Invoke 会阻塞后台线程直到 UI 线程完成更新。在你的场景中更新频率低每 100ms 或更低阻塞时间短性能影响可以忽略。替代异步方案如果需要非阻塞更新可以结合 async/await 和事件驱动模型如前述优化方案但仍使用 Invoke 确保 UI 更新一致性。优化方案移除 Application.DoEvents() 并使用 Invoke以下是具体优化代码解决状态不一致问题并提高性能。优化 ControlHardWareStaus使用 Invoke 替代 BeginInvoke确保 UI 更新同步。优化代码csharppublic partial class ControlHardWareStaus : UserControl { private string m_HardName ; private string m_Content ; private string m_Status ; private HardwareConnectStatus m_HardwareConnectStatus HardwareConnectStatus.Idle; public ControlHardWareStaus() { InitializeComponent(); } public string HardName { get m_HardName; set { if (m_HardName value) return; m_HardName value; if (!string.IsNullOrEmpty(value)) { UpdateLabel(labelName, value); } } } public string Content { get m_Content; set { if (m_Content value) return; m_Content value; if (!string.IsNullOrEmpty(value)) { UpdateLabel(labelContent, value); } } } public string Status { get m_Status; set { if (m_Status value) return; m_Status value; if (!string.IsNullOrEmpty(value)) { UpdateLabel(labelStatus, value); } } } public HardwareConnectStatus HardwareConnectStatus { get m_HardwareConnectStatus; set { if (m_HardwareConnectStatus value) return; m_HardwareConnectStatus value; UpdateBackgroundImage(); } } private void UpdateLabel(Label label, string value) { if (InvokeRequired) { Invoke((MethodInvoker)(() label.Text value)); } else { label.Text value; } } private void UpdateBackgroundImage() { if (InvokeRequired) { Invoke((MethodInvoker)(() { BackgroundImage m_HardwareConnectStatus switch { HardwareConnectStatus.Idle Properties.Resources.scralballpane资源_53, HardwareConnectStatus.InUsing Properties.Resources.scralballpane资源_54, HardwareConnectStatus.Malfunction Properties.Resources.scralballpane资源_55, _ BackgroundImage }; })); } else { BackgroundImage m_HardwareConnectStatus switch { HardwareConnectStatus.Idle Properties.Resources.scralballpane资源_53, HardwareConnectStatus.InUsing Properties.Resources.scralballpane资源_54, HardwareConnectStatus.Malfunction Properties.Resources.scralballpane资源_55, _ BackgroundImage }; } } private void labelContent_Click(object sender, EventArgs e) { if (m_HardwareConnectStatus HardwareConnectStatus.Malfunction) { string errHardName HardName; string errSeason Unknown error; Dictionarystring, object ErrorCodeMap HardwareMgr.ErrorCodeMap; Hardware hardware HardwareMgr.GetHardWare(HardName); if (hardware ! null ErrorCodeMap.ContainsKey(hardware.HardClass)) { if (ErrorCodeMap[hardware.HardClass] is Dictionarystring, object CodeMap CodeMap.ContainsKey(m_Content)) { errSeason GlobalCache.Language CN ? (CodeMap[m_Content] as Dictionarystring, object)[CN].ToString() : (CodeMap[m_Content] as Dictionarystring, object)[EN].ToString(); } } CustomMsgBox.Show(errSeason, errHardName, MessageBoxButtons.OK); } } }说明使用 Invoke 替代 BeginInvoke确保 labelContent.Text 和 BackgroundImage 同步更新。抽取 UpdateLabel 和 UpdateBackgroundImage提高代码可读性。优化 ControlMonitor移除 Application.DoEvents()在 UpdataBIBStatus 等方法中使用 Invoke 合并更新。优化代码csharppublic partial class ControlMonitor : UserControl, ILanguage { private readonly object _uiLock new object(); private readonly ConcurrentDictionarystring, ControlHardWareStaus EvnChamberControls new ConcurrentDictionarystring, ControlHardWareStaus(); private readonly ConcurrentDictionarystring, ControlHardWareStaus PowerSupplyControls new ConcurrentDictionarystring, ControlHardWareStaus(); private readonly ConcurrentDictionarystring, ControlHardWareStaus TestChannelControls new ConcurrentDictionarystring, ControlHardWareStaus(); private readonly ConcurrentDictionarystring, ControlHardWareStaus BIBControls new ConcurrentDictionarystring, ControlHardWareStaus(); private readonly ConcurrentDictionarystring, ControlHardWareStaus HeatBoardControls new ConcurrentDictionarystring, ControlHardWareStaus(); private readonly ConcurrentDictionarystring, ControlHardWareStaus AUXBoardControls new ConcurrentDictionarystring, ControlHardWareStaus(); private bool Monitor false; public ControlMonitor() { InitializeComponent(); SetStyle(ControlStyles.UserPaint, true); SetStyle(ControlStyles.AllPaintingInWmPaint, true); SetStyle(ControlStyles.OptimizedDoubleBuffer, true); Monitor true; if (!DesignMode) { CreateHardWareStatusControls(); UpdataAUXStatus(); UpdataChamberStatus(); UpdataPowerStatus(); UpdataChannelStatus(); UpdataBIBStatus(); UpdataHeatBoardStatus(); MonitorHardWareStatus(); } } private void MonitorHardWareStatus() { Thread thr new Thread(new ThreadStart(_MonitorHardWareStatus)) { IsBackground true }; thr.Start(); } private void _MonitorHardWareStatus() { while (Monitor) { UpdataAUXStatus(); UpdataChamberStatus(); UpdataPowerStatus(); UpdataChannelStatus(); UpdataBIBStatus(); UpdataHeatBoardStatus(); Thread.Sleep(1000); // 移除 Application.DoEvents() } } private void UpdataBIBStatus() { lock (_uiLock) { foreach (EvnChamber Chamber in HardwareMgr.ChamberMap.Values) { foreach (string secKey in Chamber.TestAreaMap.Keys) { TestSection Ts Chamber.TestAreaMap[secKey]; foreach (string key in Ts.TestChannelMap.Keys) { TestChannel Tc Ts.TestChannelMap[key]; HardwareConnectStatus channelStatus; string bibStr; switch (Tc.BIBStatus) { case 0: channelStatus HardwareConnectStatus.Idle; bibStr UNCONNECTED; break; case 1: channelStatus HardwareConnectStatus.InUsing; bibStr CONNECTED; break; default: continue; } if (BIBControls.TryGetValue(Tc.Id _BIB, out var control)) { if (control.InvokeRequired) { control.Invoke((MethodInvoker)delegate { control.HardwareConnectStatus channelStatus; control.Content bibStr; Console.WriteLine($BIB {Tc.Id}: Status{channelStatus}, Content{bibStr}); }); } else { control.HardwareConnectStatus channelStatus; control.Content bibStr; Console.WriteLine($BIB {Tc.Id}: Status{channelStatus}, Content{bibStr}); } } } } } } } // 类似优化 for UpdataAUXStatus, UpdataChamberStatus, UpdataPowerStatus, UpdataChannelStatus, UpdataHeatBoardStatus }说明移除 Application.DoEvents()依赖 UI 线程的自然消息处理。使用 Invoke 合并 HardwareConnectStatus 和 Content 的更新确保原子性。使用 ConcurrentDictionary 和 lock 提高线程安全性。添加日志记录便于调试。详解1. Application.DoEvents() 的意义与问题意义在 _MonitorHardWareStatus 中Application.DoEvents() 强制 UI 线程处理消息队列中的 BeginInvoke 委托确保 ControlHardWareStaus 的 UI 更新如 labelContent.Text 和 BackgroundImage及时显示。避免 UI 卡顿允许用户交互如点击 labelContent在轮询期间响应。问题状态不一致Application.DoEvents() 可能导致 UI 线程在不恰当的时机处理 BeginInvoke 委托造成 Content 和 HardwareConnectStatus 的更新顺序错乱。不可预测性处理所有消息可能触发意外的事件如 labelContent_Click干扰后台线程逻辑。性能开销频繁调用增加 UI 线程负担。调试复杂消息处理的不可控性使状态不一致问题难以追踪。替代方案移除 Application.DoEvents()依赖 UI 线程的自然消息循环。使用事件驱动模型如前述方案或异步编程async/await通过事件通知 UI 更新。2. 使用 Invoke 替代 BeginInvoke 的理由状态一致性BeginInvoke 的异步性导致 Content 和 HardwareConnectStatus 的更新可能在消息队列中乱序造成“UNCONNECTED”与 scralballpane资源_54.png 不匹配。Invoke 同步执行合并更新到一个委托确保 Content 和 BackgroundImage 同时更新避免乱序。简化消息队列Invoke 减少消息队列中的委托数量降低事件循环负担。适合场景你的更新频率低每秒或更低Invoke 的阻塞开销可忽略。同步更新保证 UI 显示与硬件状态一致。实现方式在 UpdataBIBStatus 等方法中将 Content 和 HardwareConnectStatus 的更新合并到一个 Invoke 委托。在 ControlHardWareStaus 中使用 Invoke 更新 labelContent.Text 和 BackgroundImage。实施步骤立即实施修改 ControlHardWareStaus 的 Content、Status 和 HardwareConnectStatus 使用 Invoke如优化代码。在 UpdataBIBStatus 等方法中合并更新到单个 Invoke。移除 _MonitorHardWareStatus 中的 Application.DoEvents()。中期优化使用 ConcurrentDictionary 替换普通 Dictionary确保线程安全。添加日志记录追踪更新顺序。长期优化结合前述事件驱动和异步编程模型async/await移除轮询采用事件通知。实现真实的硬件接口优化状态查询。测试验证模拟快速状态变化Tc.BIBStatus 从 0 到 1检查 UI 是否一致。使用日志验证更新顺序。预期效果状态一致性Invoke 确保 Content 和 HardwareConnectStatus 原子性更新解决不一致问题。性能优化移除 Application.DoEvents() 减少消息队列负担。可维护性同步更新和日志记录便于调试。通过移除 Application.DoEvents() 并使用 Invoke 替代 BeginInvoke你的程序将更稳定UI 显示一致且为后续事件驱动和异步编程优化奠定基础。异步事件处理多线程编程
返回列表