我使用BackgroundWorker 每30 秒更新一次表单上的控件。 BackgroundWorker 打开 COM 端口并从串行仪器读取数据。 它断言 ReportProgress() 来更新表单上的控件。 我的表单还包含一个切换开关,可打开与BackgroundWorker 相同的COM 端口以更改仪器的状态。 当切换开关的 StateChanged 事件触发时,我需要停止 BackgroundWorker 的运行,以避免竞争条件并将对 COM 端口的访问限制为一个线程。
以下是我的代码摘录:
private void BackgroundWorkerStatus_DoWork(object sender, DoWorkEventArgs e)
{
try
{
while (!BackgroundWorkerStatus.CancellationPending)
{
// Get new values (Open COM Port, read state)
ReadState();
// Update the User Interface
BackgroundWorkerStatus.ReportProgress(0);
// Wait before updating again
Thread.Sleep(UpdateInterval);
}
}
catch
{
MessageBox.Show(….)
}
}
private void BackgroundWorkerStatus_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
try
{
// Update controls on the Form
}
catch
{
MessageBox.Show(….);
}
private void ToggleSwitch_StateChanged(object sender, NationalInstruments.UI.ActionEventArgs e)
{
// Open COM Port, set state, update Form
}
我尝试在 StateChanged 事件开始时调用 CancelAsync() 并在结束时调用 RunWorkerAsync() 。
我尝试创建一个在 StateChanged 事件中设置并在 DoWork 方法中求值的全局布尔值。
这些都不起作用,大概是因为 CancelPending 和全局布尔值不是连续计算的,并且它们的状态在BackgroundWorker线程运行期间发生变化。
我也尝试过使用信号量,但这会在后台线程尝试更新 UI 时导致跨线程异常。 我明白为什么,但不知道如何解决。
本质上我需要 Semaphore 和 BackgroundWorker 的组合来同步后台任务,同时也可以更新 UI。
执行此操作的适当方法是什么?
谢谢!
你是对的,直接修改标志或使用来自不同线程的信号量可能会导致竞争条件。以下是如何有效同步您的BackgroundWorker和切换开关:
1。使用手动重置事件:
此事件允许您向BackgroundWorker 线程发出信号以正常停止。
声明一个名为
ManualResetEvent
的私有 stopEvent
变量。
在
BackgroundWorkerStatus_DoWork
:
private void BackgroundWorkerStatus_DoWork(object sender, DoWorkEventArgs e)
{
try
{
while (!stopEvent.WaitOne(UpdateInterval)) // Wait for either UpdateInterval or stop signal
{
if (BackgroundWorkerStatus.CancellationPending)
{
break;
}
// Get new values (Open COM Port, read state)
ReadState();
// Update the User Interface (use Invoke for thread-safe access)
this.Invoke(new Action(() => UpdateControls()));
// Wait before updating again
}
}
catch
{
MessageBox.Show(….)
}
}
我们使用带超时功能的
WaitOne
来检查 stopEvent
是否发出信号或 CancellationPending
标志变为 true。这样可以在翻转切换开关时正常关闭。
我们使用
Invoke
从后台线程安全地更新 UI 控件。
在
ToggleSwitch_StateChanged
:
private void ToggleSwitch_StateChanged(对象发送者,NationalInstruments.UI.ActionEventArgs e) { 如果(切换开关。选中) { // 打开 COM 端口,设置状态(此处为您的代码) // 更新表单(使用 Invoke 进行线程安全访问) this.Invoke(new Action(() => UpdateForm())); } 别的 { stopEvent.Set(); // 通知BackgroundWorker线程停止 } }
如果切换开关被选中(意味着通信已启用),请照常执行 COM 端口操作并更新表单。
如果未选中切换开关(通信已禁用),请设置
stopEvent
以指示 BackgroundWorker 线程停止。
2。考虑锁定机制(可选):
如果您需要确保从BackgroundWorker线程或切换开关事件处理程序对COM端口的独占访问,您可以使用
Mutex
进行互斥。但是,请谨慎使用,因为过度锁定会影响性能。
3.记得清理:
stopEvent.Close()
来释放
事件句柄。此方法允许在翻转切换开关时彻底关闭
BackgroundWorker
线程,并确保与 COM 端口和 UI 更新的线程安全通信。