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

Blazor Server多客户端读取远程PLC数据异常问题求助

问题描述

我在Blazor Server应用中用第三方DLL连接远程PLC读取数据,该DLL的doNCK_ReadMixEx方法仅支持静态变量(不用静态的话读不出数据)。我把这个方法做成Scoped服务,想让每个客户端能同时周期性(每秒一次)读取不同PLC的数据。

单客户端读取完全正常,但两个客户端同时读不同PLC时,数据读不全,还频繁出现乱码或错误值。不确定是方法调用问题还是静态变量导致的,代码如下:


Razor页面(触发周期性读取)

@page "/PLC_Status"
@inject Deltalogic Deltalogic
@inject Parameter_840D Parameter_840D
<button @onclick="Read_CNC_Status">Start Reading</button>

Razor.cs

public async Task Read_CNC_Status()
{
    Reading_active = true;
                
    for (int k=0; k<=200;k++)
    {
        Read_PLC_Data();
        InvokeAsync(() =>
        {
            StateHasChanged();
        });
        await Task.Delay(1000);

        if (k == 200)
        {
            Stop_CNC_Status();
        }
    }
}

public void Read_PLC_Data()
//Here the doNCK_ReadMixEx is called for each PLC parameter 
{
   Parameter_840D.Par_840D_Channel_Status(Deltalogic.channel_no); //Pick-up parameters for variable-1          
   Deltalogic.doNCK_ReadMixEx(Deltalogic.MAE_Status_DevNr, 5000, out Deltalogic.MAE_Status_Mode);
   Parameter_840D.Par_840D_Prog_Status(Deltalogic.channel_no); //Pick-up parameters for variable-2          
   Deltalogic.doNCK_ReadMixEx(Deltalogic.MAE_Status_DevNr, 5000, out Deltalogic.MAE_Status_State_CH[1]);
   Parameter_840D.Par_840D_Machine_Active_Workpiece(Deltalogic.channel_no);//Pick-up parameters for variable-3          
   Deltalogic.doNCK_ReadMixEx(Deltalogic.MAE_Status_DevNr, 5000, out Deltalogic.MAE_Status_WPD_CH[Deltalogic.channel_no]);
// and other PLC parameters....
}

Parameter_840D(doNCK_ReadMixEx参数配置Scoped服务)

// The parameters of doNCK_ReadMixEx have to be defined as static. If not, data cannot be read
public class Parameter_840D
{
    [Inject]
    Deltalogic Deltalogic { get; set; }

    public static byte channel_no = 1;
    public static ushort Parameter_No;
    public static ushort column;
    public static AGL4.NCK_Block block_type;
    public static AGL4.NCK_Area area;
    public static ushort row;
    public static byte rowCount;
    public static byte Unit;
    public static int Bufflen;
    public static byte Buff;
    public static AGL4.NCK_DDEVarFormat DDEVarType;

    //Methods that just describes the parameter for each PLC variable
}

Deltalogic(PLC读取方法Scoped服务)

public class Deltalogic
{
    [Inject]
    Parameter_840D Parameter_840D { get; set; }

    public Int32 doNCK_ReadMixEx(Int32 connnr, Int32 timeout, out string NCK_Data)
    {
        //Reset_Parameter_value();
        NCK_Data = "";
        Parameter_840D Parameter_840D = new Parameter_840D();
        connnr = Return_Connect_NCK;
        Int32 result = 0;
        AGL4.NckDataRW[] rwfield = new AGL4.NckDataRW[1];
        rwfield[0] = new AGL4.NckDataRW();
        rwfield[0].Area = Parameter_840D.area;
        rwfield[0].Block = Parameter_840D.block_type;
        rwfield[0].Column = Parameter_840D.column;
        rwfield[0].Row = Parameter_840D.row;
        rwfield[0].RowCount = Parameter_840D.rowCount;
        rwfield[0].Unit = Parameter_840D.Unit;
        rwfield[0].DDEVarType = Parameter_840D.DDEVarType;
        rwfield[0].BuffLen = Parameter_840D.Bufflen;
        rwfield[0].Buff = new Byte[rwfield[0].BuffLen];          
        rwfield[0].Result = 0;
        result = AGL4.NCK_ReadMixEx(connnr, ref rwfield, timeout);
        return result;
    }
}

问题根源分析
  1. 静态变量共享冲突:Parameter_840D里的所有参数都是静态的,静态变量属于类本身而非实例。Blazor Server的Scoped服务虽然每个客户端有独立实例,但静态变量会被所有客户端共享。当两个客户端同时修改静态参数时,会互相覆盖,导致doNCK_ReadMixEx拿到错误配置去读PLC,出现乱码或数据错误。
  2. 错误的实例创建:Deltalogic的doNCK_ReadMixEx方法里手动new了Parameter_840D实例,绕过了依赖注入的Scoped实例,同时因为参数是静态的,新实例依然使用共享变量,加剧冲突。
  3. 竞态条件:周期性读取的同步方法没有线程安全保护,多个客户端线程同时操作静态变量,引发数据混乱。

解决方案

方案1:重构参数传递,弃用静态变量(优先推荐)

如果第三方DLL并非真的强制要求静态变量(可能是之前的误解),把Parameter_840D改为非静态类,用实例成员存储参数:

public class Parameter_840D
{
    public byte channel_no = 1;
    public ushort Parameter_No;
    public ushort column;
    public AGL4.NCK_Block block_type;
    public AGL4.NCK_Area area;
    public ushort row;
    public byte rowCount;
    public byte Unit;
    public int Bufflen;
    public byte Buff;
    public AGL4.NCK_DDEVarFormat DDEVarType;

    // 方法改为操作当前实例的参数
    public void Par_840D_Channel_Status(byte channelNo)
    {
        this.channel_no = channelNo;
        // 配置当前实例的其他参数...
    }
}

修改Deltalogic方法,接收Parameter_840D实例作为参数:

public class Deltalogic
{
    public Int32 doNCK_ReadMixEx(Int32 connnr, Int32 timeout, Parameter_840D param, out string NCK_Data)
    {
        NCK_Data = "";
        connnr = Return_Connect_NCK;
        Int32 result = 0;
        AGL4.NckDataRW[] rwfield = new AGL4.NckDataRW[1];
        rwfield[0] = new AGL4.NckDataRW();
        rwfield[0].Area = param.area;
        rwfield[0].Block = param.block_type;
        rwfield[0].Column = param.column;
        rwfield[0].Row = param.row;
        rwfield[0].RowCount = param.rowCount;
        rwfield[0].Unit = param.Unit;
        rwfield[0].DDEVarType = param.DDEVarType;
        rwfield[0].BuffLen = param.Bufflen;
        rwfield[0].Buff = new Byte[rwfield[0].BuffLen];          
        rwfield[0].Result = 0;
        result = AGL4.NCK_ReadMixEx(connnr, ref rwfield, timeout);
        // 处理NCK_Data赋值逻辑
        return result;
    }
}

Razor.cs调用修改为:

public void Read_PLC_Data()
{
    Parameter_840D.Par_840D_Channel_Status(Deltalogic.channel_no);
    Deltalogic.doNCK_ReadMixEx(Deltalogic.MAE_Status_DevNr, 5000, Parameter_840D, out Deltalogic.MAE_Status_Mode);
    // 其他参数读取同理
}

方案2:强制静态变量场景下加线程锁

如果第三方DLL确实要求静态变量,必须在访问静态参数时加线程锁,确保同一时间只有一个客户端操作静态变量:
在Parameter_840D添加静态锁对象:

public class Parameter_840D
{
    private static readonly object _lockObj = new object();
    // 保留原有静态参数...

    public void Par_840D_Channel_Status(byte channelNo)
    {
        lock(_lockObj)
        {
            channel_no = channelNo;
            // 配置其他静态参数...
        }
    }
}

修改Deltalogic的方法,加锁执行读取:

public Int32 doNCK_ReadMixEx(Int32 connnr, Int32 timeout, out string NCK_Data)
{
    lock(Parameter_840D._lockObj)
    {
        NCK_Data = "";
        connnr = Return_Connect_NCK;
        Int32 result = 0;
        AGL4.NckDataRW[] rwfield = new AGL4.NckDataRW[1];
        rwfield[0] = new AGL4.NckDataRW();
        rwfield[0].Area = Parameter_840D.area;
        // 其他参数赋值...
        result = AGL4.NCK_ReadMixEx(connnr, ref rwfield, timeout);
        return result;
    }
}

注意:此方案会让客户端读取操作串行化,性能受影响,适合客户端数量较少的场景。

额外优化

  • 用Timer替代for循环+Task.Delay实现周期性读取,避免占用组件线程。
  • 确保每个客户端的PLC连接(Return_Connect_NCK对应的连接)是独立实例,不要共享连接。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.24 07:09:19