在调用ArgumentOutOfRangeException: Non-negative number required.
类的ReadTo()
方法时,我的代码会不确定地抛出SerialPort
:
public static void RetrieveCOMReadings(List<SuperSerialPort> ports)
{
Parallel.ForEach(ports,
port => port.Write(port.ReadCommand));
Parallel.ForEach(ports,
port =>
{
try
{
// this is the offending line.
string readto = port.ReadTo(port.TerminationCharacter);
port.ResponseData = port.DataToMatch.Match(readto).Value;
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
port.ResponseData = null;
}
});
}
SuperSerialPort
是SerialPort
类的扩展,主要用于保存端口上每个设备特定通信所需的信息。
端口始终定义TerminationCharacter
;
大部分时间它都是换行符:
我不明白为什么会发生这种情况 如果ReadTo无法找到输入缓冲区中指定的字符,那么它不应该超时并且不返回任何内容吗?
StackTrace指向mscorlib中的一个违规函数,在SerialPort类的定义中:
System.ArgumentOutOfRangeException occurred
HResult=-2146233086
Message=Non-negative number required.
Parameter name: byteCount
Source=mscorlib
ParamName=byteCount
StackTrace:
at System.Text.ASCIIEncoding.GetMaxCharCount(Int32 byteCount)
InnerException:
我跟着它,这是我找到的:
private int ReadBufferIntoChars(char[] buffer, int offset, int count, bool countMultiByteCharsAsOne)
{
Debug.Assert(count != 0, "Count should never be zero. We will probably see bugs further down if count is 0.");
int bytesToRead = Math.Min(count, CachedBytesToRead);
// There are lots of checks to determine if this really is a single byte encoding with no
// funky fallbacks that would make it not single byte
DecoderReplacementFallback fallback = encoding.DecoderFallback as DecoderReplacementFallback;
----> THIS LINE
if (encoding.IsSingleByte && encoding.GetMaxCharCount(bytesToRead) == bytesToRead &&
fallback != null && fallback.MaxCharCount == 1)
{
// kill ASCII/ANSI encoding easily.
// read at least one and at most *count* characters
decoder.GetChars(inBuffer, readPos, bytesToRead, buffer, offset);
bytesToRead
被分配了一个负数,因为CachedBytesToRead
是否定的。内联注释指出CachedBytesToRead
永远不会是负面的,但情况显然如此:
private int readPos = 0; // position of next byte to read in the read buffer. readPos <= readLen
private int readLen = 0; // position of first unreadable byte => CachedBytesToRead is the number of readable bytes left.
private int CachedBytesToRead {
get {
return readLen - readPos;
}
任何人都有理由解释为什么会发生这种情况?
我不相信我在阅读/写入/访问SerialPorts方面做了非法的事情。
这种情况不断被抛出,没有好的方法来重现它
输入缓冲区中有可用的字节,这里可以看到一些关键属性在中断时的状态(readLen,readPos,BytesToRead,CachedBytesToRead):
我做错了什么?
编辑:一张图片显示同一个端口不能从循环中异步访问:
答案 0 :(得分:3)
这在技术上是可行的,通常是.NET类的非线程安全的常见问题。但是,SerialPort类没有,需要是线程安全的。
粗略的诊断是两个独立的线程同时在同一个SerialPort对象上调用ReadTo()。更新 readPos 变量的代码中将出现标准线程争用情况。两个线程都从缓冲区复制了相同的数据,每个都增加了readPos。实际上,将readPos提前了两倍。 Kaboom在下次调用时发生readPos大于readLen,产生缓冲区中可用字节数的负值。
简单的解释是,您的List<SuperSerialPort>
集合不止一次包含相同的端口。 Parallel.ForEach()语句触发竞争。工作正常一段时间,直到两个线程同时执行decoder.GetChars()方法,并且都到达下一个语句:
readPos += bytesToRead;
测试假设的最佳方法是添加代码,以确保列表确实包含多个相同的端口。大致是:
#if DEBUG
for (int ix = 0; ix < ports.Count - 1; ++ix)
for (int jx = ix + 1; jx < ports.Count; ++jx)
if (ports[ix].PortName == ports[jx].PortName)
throw new InvalidOperationException("Port used more than once");
#endif
第二种解释是您的方法正在被多个线程调用。这不起作用,你的方法不是线程安全的。没有用锁来保护它,确保只有一个线程可以调用它是合乎逻辑的修复。
答案 1 :(得分:0)
可能是因为您正在设置终止字符并将此字符用于readto。而是尝试使用ReadLine或删除终止字符。