我有两个定时器。其中一个计时器从plc检索数据并更新datatable中的相关数据行。在另一个计时器中,我将datatable作为参数发送到存储过程。问题是,有时我的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叠迹;
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)


发布于 2015-03-17 05:34:39
如果问题确实是由两个线程同时处理同一个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的额外代码,并且不希望该代码被阻塞,等待第二个事件处理程序,则可以将其放在if (mut.WaitOne(iMaxWaitMSec))块之外。
更新
根据你的评论,以下是我对如何安排整个节目的想法。
主要目标是尽量减少两个线程可能等待对方的时间。
1)确保使用多线程计时器:System.Timers.Timer或System.Threading.Timer,而不是System.Windows.Forms.Timer。https://msdn.microsoft.com/en-us/library/system.timers.timer(v=vs.110).aspx
我希望timer事件处理程序在一个单独的线程上运行。
如果对经过的事件的处理持续时间超过间隔,则可能在另一个ThreadPool线程上再次引发该事件。
因此,有一个标志,指示正在处理的事件,并检查它。我不认为您想再次调用您的存储过程,而之前调用它的尝试还没有完成。
2)在内存中有一个可以用数据容纳队列的结构。第一个定时器会定期从PLC读取数据,并将数据追加到队列的末尾。第二个定时器将定期检查队列并从队列的开头选择挂起的数据。有一个类Queue。理想情况下,它应该能够快速地将元素追加到其末尾,并从一开始就快速删除元素。在.NET 4中有ConcurrentQueue,这意味着您不需要显式的Mutexe。
如果将数据插入数据库突然变得缓慢(即网络关闭),队列将增长并包含多个元素。在这种情况下,应该由您来决定要做什么--丢弃额外的元素,或者仍然尝试插入所有这些元素。
3)应该只使用Mutex来防止对这个“队列”对象的同时访问,以减少等待。
// 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
}https://stackoverflow.com/questions/29028208
复制相似问题