SqlCommand中的ArgumentOutOfRangeException

时间:2015-03-13 08:55:39

标签: c# sql timer sqlcommand

我有两个计时器。其中一个计时器从plc检索数据并更新数据表中的相关数据行。在另一个计时器中,我将该数据表作为参数发送到存储过程。问题是,有时我的sqlCommand.ExecuteNonQuery()给了我一个ArgumentOutOfRangeException。我的数据表中有128行。我从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

Stack Trace;

   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)

enter image description here

enter image description here

1 个答案:

答案 0 :(得分:1)

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

  

当两个或多个线程需要同时访问共享资源时   时间,系统需要一个同步机制来确保只   一次一个线程使用该资源。互斥是一种同步   原语仅授予对共享资源的独占访问权限   一个线程。如果一个线程获得了一个互斥锁,那么第二个线程需要   获取该互斥锁暂停,直到第一个线程释放   互斥。

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

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

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,并且您不希望阻止该代码等待第二个事件处理程序,您可以将其放在{{{ 1}}阻止。

<强>更新

根据您的意见,以下是我对如何安排整个计划的想法。

主要目标是最小化两个线程可能相互等待的时间。

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

我希望timer事件处理程序在一个单独的线程上运行。

  

如果Elapsed事件的处理持续时间超过Interval,那么   可能会在另一个ThreadPool线程上再次引发该事件。

因此,有一个标志,指示正在处理事件并检查它。我不认为你想要再次调用你的存储过程,而之前尝试调用它还没有完成。

2)在内存中有一个可以保存数据队列的结构。 第一个定时器将定期从PLC读取数据并将数据附加到队列的末尾。第二个计时器会定期检查队列并从队列的开头挑选待处理的数据。 有一个班级Queue。理想情况下,它应该能够快速将元素附加到其末尾并从头开始快速删除元素。在.NET 4中有ConcurrentQueue,这意味着您不需要显式的互斥锁。

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

3)Mutex应该用于防止同时访问这个&#34;队列&#34;最小化等待的对象。

System.Windows.Forms.Timer

...

// 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);
}