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

C# WinForm ChartControl实时绘制时偶发0值异常问题求助

问题根因
  • 异常未拦截后续逻辑:当前代码无论PLC读取是否抛出异常,都会执行添加数据点的逻辑。如果读取PLC失败进入catch块,atolyevals要么是未初始化的默认值(数组元素全为0),要么是历史脏值,直接取值就会产生0值数据点。
  • 跨线程操作UI绑定资源:你使用的System.Threading.Timer回调运行在后台线程池线程,直接修改绑定到Chart控件的数据源dp1属于非法跨线程UI操作,系统负载升高(比如打开其他应用)时更容易出现数据竞争,读取到脏值。
  • 定时器重入问题:如果单次PLC读取+数据处理的耗时超过定时器间隔(比如PLC响应超时、系统资源紧张),多轮定时器回调会并发执行,同时读写atolyevals、dp1等共享变量,会出现值覆盖、读取到半初始化数组等问题,导致拿到0值。
  • 缺失数据合法性校验:读取到PLC值后没有做范围校验,直接写入数据源,异常值不会被过滤。
修复方案

1. 异常分支拦截数据写入

将添加数据点的逻辑移到读取成功的分支下,避免读取失败时写入无效值,同时增加合法性校验:

bool readSuccess = false;
try
{
    atolyevals = atolye.ReadHoldingRegisters(2001, 32);
    readSuccess = true;
    if (a_s != null)
    {
        a_s = null;
        lineAdd(simdi() + " Atölye PLC bağlantısı sağlandı","g");
    }
}
catch
{
    // 保留原有错误检查、日志记录逻辑
}

if(readSuccess)
{
    var currentVal = gazatolyevals[0];
    // 按实际PLC量程修改校验范围,过滤异常值
    if(currentVal >= 0 && currentVal <= 1000) 
    {
        // 数据点添加逻辑移到此处
        dp1.Add(new DataPoint(DateTime.Now, currentVal));
        if (dp1.Count > ViewportPointCount)
            dp1.RemoveAt(0);
    }
}

2. 修复跨线程UI操作问题

修改UI绑定的数据源必须切换到UI线程执行,WinForm环境下用Invoke封装数据修改逻辑:

if(readSuccess)
{
    var currentVal = gazatolyevals[0];
    if(currentVal >= 0 && currentVal <= 1000) 
    {
        if(this.InvokeRequired)
        {
            this.Invoke(new Action(() => 
            {
                dp1.Add(new DataPoint(DateTime.Now, currentVal));
                if (dp1.Count > ViewportPointCount)
                    dp1.RemoveAt(0);
            }));
        }
        else
        {
            dp1.Add(new DataPoint(DateTime.Now, currentVal));
            if (dp1.Count > ViewportPointCount)
                dp1.RemoveAt(0);
        }
    }
}

如果是WPF环境,将上述Invoke替换为Dispatcher.Invoke即可。

3. 防止定时器重入

增加运行状态标记,避免多轮回调并发执行:

// 类级别添加运行状态标记
private volatile bool _isTimerRunning = false;

private void Timer_Tick()
{
    // 上一轮任务未执行完则直接跳过本轮
    if(_isTimerRunning) return;
    try
    {
        _isTimerRunning = true;
        // 原有PLC读取、数据处理逻辑放在此处
    }
    finally
    {
        // 无论是否出现异常都要重置状态
        _isTimerRunning = false;
    }
}

可选优化

如果对实时性要求不高,可直接将System.Threading.Timer替换为WinForm自带的System.Windows.Forms.Timer,该定时器回调天然运行在UI线程,无需额外处理跨线程问题,注意不要在回调中执行过于耗时的逻辑避免卡UI即可。


内容的提问来源于stack exchange,提问作者Mustafa Ozbalci

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.29 05:15:03