SerialPort.ReadTo()上的ArgumentOutOfRangeException

时间:2014-04-21 15:54:07

标签: c# .net winforms serial-port parallel.foreach

在调用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;
            }
        });
}

SuperSerialPortSerialPort类的扩展,主要用于保存端口上每个设备特定通信所需的信息。
端口始终定义TerminationCharacter;
大部分时间它都是换行符:

enter image description here

我不明白为什么会发生这种情况 如果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):

enter image description here

我做错了什么?


编辑:一张图片显示同一个端口不能从循环中异步访问:enter image description here

2 个答案:

答案 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或删除终止字符。