WPF后台线程执行长时任务无法响应UI停止信号问题
问题描述
这是一个信号采集程序,通过getDataEx函数实现信号采集。在WPF中点击启动按钮后,getDataEx函数在后台线程运行,采集的数据可正常显示在UI上,但点击停止按钮后无任何效果。
C++ DLL代码:
//C++代码 extern "C" __declspec(dllexport) void getDataEx(void (*funcptr)(short), WORD wPeriod, DWORD data_count, bool& stopSignal); void getDataEx(void (*funcptr)(short), WORD wPeriod, DWORD data_count, bool& stopSignal) { ... while (data_count > 0 && !stopSignal) { Sleep(dwdelayTime); data_count--; ... } }
C# WPF代码:
//C# WPF代码 [DllImport("*.dll")] private extern static void getDataEx(DataCallback funcPtr, ushort wPeriod, uint data_count, ref bool stopSignal); private bool stopSignal = false; private void StartDataAcquisition() { if (isScan && isOpen) { dataAcquisitionThread = new Thread(() => { // 在C#中定义回调函数 DataCallback callbackFunction = (value) => { float pressure_value = (float)(value / 65535.0 * 10); // 在主线程更新UI Dispatcher.Invoke(() => { ... }); }; getDataEx(callbackFunction, wPeriod, sample_times, ref stopSignal); }); dataAcquisitionThread.Start(); } } //停止逻辑 private void btn_stop_Click(object sender, RoutedEventArgs e) { stopSignal = true; }
已知:若在运行getDataEx函数前设置stopSignal = true;,该信号可正常传递至getDataEx函数中。
原因分析
核心问题出在线程可见性和跨语言类型内存布局不匹配:
- 线程可见性缺失:C#的
bool变量stopSignal未标记为volatile,后台线程(调用DLL的线程)可能会缓存该变量的副本,UI线程修改后,后台线程无法读取到内存中的最新值。 - C++编译器优化:C++编译器可能会对循环条件
!stopSignal做优化,将值缓存到寄存器中,即使内存中的stopSignal已改变,循环也不会读取最新值。 - 跨语言类型匹配问题:C#的
bool与C++的bool虽然通常都是1字节,但跨平台或编译器配置不同时可能存在内存布局差异,通过ref传递非托管代码时可能出现访问不一致。
解决方案
1. 修复线程可见性(C#端)
将stopSignal标记为volatile,强制编译器每次读取都从内存获取最新值:
private volatile bool stopSignal = false;
2. 统一跨语言类型定义(推荐)
为彻底避免类型布局问题,改用明确的整数类型替代bool:
C++端修改
extern "C" __declspec(dllexport) void getDataEx(void (*funcptr)(short), WORD wPeriod, DWORD data_count, volatile char& stopSignal); void getDataEx(void (*funcptr)(short), WORD wPeriod, DWORD data_count, volatile char& stopSignal) { ... // 用0/1替代bool的true/false判断 while (data_count > 0 && stopSignal == 0) { Sleep(dwdelayTime); data_count--; ... } }
C#端修改
private volatile byte stopSignal = 0; [DllImport("*.dll")] private extern static void getDataEx(DataCallback funcPtr, ushort wPeriod, uint data_count, ref byte stopSignal); // 停止逻辑 private void btn_stop_Click(object sender, RoutedEventArgs e) { stopSignal = 1; }
3. 禁止C++编译器优化(可选)
在C++中用volatile修饰stopSignal参数,确保循环每次都读取内存中的值,避免编译器优化导致的读取失效。
4. 更可靠的线程通知方案(进阶)
如果需要更稳定的跨线程通知,可以使用ManualResetEventSlim替代布尔变量:
C#端
private ManualResetEventSlim stopEvent = new ManualResetEventSlim(false); private void StartDataAcquisition() { if (isScan && isOpen) { dataAcquisitionThread = new Thread(() => { DataCallback callbackFunction = (value) => { float pressure_value = (float)(value / 65535.0 * 10); Dispatcher.Invoke(() => { ... }); }; // 将事件句柄传递给C++ getDataEx(callbackFunction, wPeriod, sample_times, stopEvent.WaitHandle.SafeWaitHandle.DangerousGetHandle()); }); dataAcquisitionThread.Start(); } } private void btn_stop_Click(object sender, RoutedEventArgs e) { stopEvent.Set(); }
C++端
extern "C" __declspec(dllexport) void getDataEx(void (*funcptr)(short), WORD wPeriod, DWORD data_count, HANDLE stopEvent) { ... // 检查事件是否被触发 while (data_count > 0 && WaitForSingleObject(stopEvent, 0) == WAIT_TIMEOUT) { Sleep(dwdelayTime); data_count--; ... } }
内容的提问来源于stack exchange,提问作者Angel are helpless
相关产品推荐
相关产品推荐

