SqlCommand 中的 ArgumentOutOfRangeException

ArgumentOutOfRangeException in SqlCommand

我有两个计时器。这些计时器之一从 plc 检索数据并更新 datatable 中的相关数据行。在另一个计时器中,我将该数据 table 作为参数发送到存储过程。问题是,有时我的 sqlCommand.ExecuteNonQuery() 会给我一个 ArgumentOutOfRangeException。我的数据中有 128 行 table。我从 plc 读取了 512 个字节。一行代表一个浮点值(即4个字节)

我无法理解异常 ArgumentOutOfRange。变量计数适合行计数。问题是什么。为什么我有时会收到此错误?

这是我的代码

        void timer1_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            timer1.Stop();
            byte[] data = new byte[512];
            int res = dc.readManyBytes(libnodave.daveDB, 19, 0, 512, data);
            if (res == 0)
            {
                for (int i = 0; i < 128; i++)
                {
                    byte[] temp = new byte[] { data[(i * 4 + 3)], data[(i * 4 + 2)], data[(i * 4 + 1)], data[(i * 4)] };
                    double value = Math.Truncate(Convert.ToDouble(BitConverter.ToSingle(temp, 0)) * 100) / 100;
                    DataRow row = dtAddress.Rows[i];
                    switch (row["DataType"].ToString())
                    {
                        case "REAL":
                            DataRow[] rValues = dtValue.Select("AddressID = " + row["ID"]);
                                foreach (DataRow rValue in rValues)
                                {
                                    rValue["Value"] = value;
                                    rValue["LastUpdate"] = DateTime.Now;
                                }
                            break;
                    }
                }
            }
       }

    void timer2_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
    {
        using (SqlCommand crudValues = new SqlCommand("dbo.crudValues", connection))
        {
            crudValues.CommandType = CommandType.StoredProcedure;
            SqlParameter param = crudValues.Parameters.AddWithValue("@tblValue", dtValue);
            param.SqlDbType = SqlDbType.Structured;

            crudValues.ExecuteNonQuery();
        }
    }

--SQL 存储过程

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO

ALTER PROCEDURE [dbo].[crudValues]
    @tblValue as dbo.tblValue READONLY
AS
BEGIN
    SET NOCOUNT ON;
    UPDATE tblValue SET tblValue.Value = t.Value, tblValue.LastUpdate = t.LastUpdate FROM tblValue INNER JOIN @tblValue t ON tblValue.ID = t.ID
END

堆栈跟踪;

   at System.Data.SqlClient.TdsParser.TdsExecuteRPC(_SqlRPC[] rpcArray, Int32 timeout, Boolean inSchema, SqlNotificationRequest notificationRequest, TdsParserStateObject stateObj, Boolean isCommandProc, Boolean sync, TaskCompletionSource`1 completion, Int32 startRpc, Int32 startParam)
   at System.Data.SqlClient.SqlCommand.RunExecuteReaderTds(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, Boolean async, Int32 timeout, Task& task, Boolean asyncWrite, SqlDataReader ds)
   at System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, String method, TaskCompletionSource`1 completion, Int32 timeout, Task& task, Boolean asyncWrite)
   at System.Data.SqlClient.SqlCommand.InternalExecuteNonQuery(TaskCompletionSource`1 completion, String methodName, Boolean sendToPipe, Int32 timeout, Boolean asyncWrite)
   at System.Data.SqlClient.SqlCommand.ExecuteNonQuery()
   at GazMotoruPLCScanner.Program.timer2_Elapsed(Object sender, ElapsedEventArgs e) in d:\Projeler\TRES ENERJİ\GazMotoruPLCScanner\Program.cs:line 106
   at System.Timers.Timer.MyTimerCallback(Object state)

如果问题确实是由两个线程同时处理同一个 DataTable 对象引起的,那么一种可能的解决方案是使用 Mutex 同步两个线程。

When two or more threads need to access a shared resource at the same time, the system needs a synchronization mechanism to ensure that only one thread at a time uses the resource. Mutex is a synchronization primitive that grants exclusive access to the shared resource to only one thread. If a thread acquires a mutex, the second thread that wants to acquire that mutex is suspended until the first thread releases the mutex.

在您的情况下,第一个事件处理程序将元素添加到 DataTable,第二个事件处理程序将此 DataTable 发送到存储过程。如果在 RunExecuteReader 尝试从中读取行时更改此对象,则任何事情都可能发生。

创建一个可以从 timer1_Elapsed()timer2_Elapsed() 访问的 Mutex class 实例。

private static Mutex mut = new Mutex();

您的计时器事件处理程序可能如下所示:

void timer1_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
    int iMaxWaitMSec = 10000;
    if (mut.WaitOne(iMaxWaitMSec))
    {
        try
        {
            // Populate DataTable
        }
        catch
        {
        }
        finally
        {
            mut.ReleaseMutex();
        }
    }
    else
    {
        // we waited longer than iMaxWaitMSec milliseconds
        // in an attempt to lock the mutex
        // skip this timer event
        // we'll retry next time
    }
}

.

void timer2_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
    int iMaxWaitMSec = 10000;
    if (mut.WaitOne(iMaxWaitMSec))
    {
        try
        {
            // Send DataTable to the database
        }
        catch
        {
        }
        finally
        {
            mut.ReleaseMutex();
        }
    }
    else
    {
        // we waited longer than iMaxWaitMSec milliseconds
        // in an attempt to lock the mutex
        // skip this timer event
        // we'll retry next time
    }
}

检查语法错误。将超时设置为某个适当的值。添加对获取互斥量时间过长情况的适当处理。

这种方法的结果是 timer1_Elapsed()timer2_Elapsed() 中的两个代码块 if (mut.WaitOne(iMaxWaitMSec)) 永远不会同时 运行。

如果您有一些不涉及共享 DataTable 的额外代码,并且您不希望该代码在等待第二个事件处理程序时被阻塞,您可以将其放在 [=24= 之外]块。

更新

根据大家的意见,以下是我对如何安排整个节目的想法。

主要目标是尽量减少两个线程可能相互等待的时间。

1) 确保使用多线程计时器:System.Timers.TimerSystem.Threading.Timer,而不是 System.Windows.Forms.Timerhttps://msdn.microsoft.com/en-us/library/system.timers.timer(v=vs.110).aspx

我希望计时器事件处理程序在单独的线程上 运行。

If processing of the Elapsed event lasts longer than Interval, the event might be raised again on another ThreadPool thread.

所以,有一个标志表明正在处理事件并检查它。我认为您不会想在上一次调用存储过程的尝试尚未完成时再次调用它。

2) 在内存中有一个结构可以保存您的数据队列。 第一个计时器会定期从 PLC 读取数据并将数据追加到队列的末尾。第二个计时器会定期检查队列并从队列的开头挑选待处理数据。 有一个 class Queue. Ideally it should be able to quickly append element to its end and quickly remove element from the beginning. In .NET 4 there is ConcurrentQueue,这意味着你不需要显式的互斥锁。

如果将数据插入数据库突然变慢(即网络中断),队列将会增长并包含多个元素。在这种情况下由您决定要做什么 - 丢弃额外的元素,或者仍然尝试插入所有元素。

3) Mutex 应该只用于防止同时访问这个 "queue" 对象以最小化等待。

// somewhere in the main program
Queue<DataTable> MainQueue = new Queue<DataTable>();
// or in .NET 4
ConcurrentQueue<DataTable> MainConcurrentQueue = new ConcurrentQueue<DataTable>();

...

void timer1_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
    // read data from PLC
    // parse, process the data
    // create a **new** instance of the DataTable object
    DataTable dt = new DataTable();
    // and fill it with your data

    // append the new DataTable object to the queue
    mut.WaitOne();
    try
    {
        MainQueue.Enqueue(dt);
    }
    catch { }
    finally
    {
        mut.ReleaseMutex();
    }

    // or in .NET4 simply
    MainConcurrentQueue.Enqueue(dt);
}

...

void timer2_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
    DataTable dt = null;

    mut.WaitOne();
    try
    {
        dt = MainQueue.Dequeue();
    }
    catch { }
    finally
    {
        mut.ReleaseMutex();
    }

    // or in .NET4 simply
    dt = MainConcurrentQueue.Dequeue();

    // Send DataTable to the database

    // TODO: add checks for empty queue
    // TODO: add checks for long queue 
    // and send all or some of the accumulated elements to the DB
}