You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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函数中。


原因分析

核心问题出在线程可见性和跨语言类型内存布局不匹配:

  1. 线程可见性缺失:C#的bool变量stopSignal未标记为volatile,后台线程(调用DLL的线程)可能会缓存该变量的副本,UI线程修改后,后台线程无法读取到内存中的最新值。
  2. C++编译器优化:C++编译器可能会对循环条件!stopSignal做优化,将值缓存到寄存器中,即使内存中的stopSignal已改变,循环也不会读取最新值。
  3. 跨语言类型匹配问题: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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 15:02:33