.net中的串口类的实时响应性分析

曾经在实时控制系统应用中,利用串口的管脚事件进行IO监听,发现在对外有大量密集的线程池输出时,会造成管脚信号丢失,在项目现场分析了相当一段时间,后来揣测是由于管脚监听事件的底层,利用了相同的线程池,当在业务系统中频繁调用底层线程池时,造成新的线程分配机制跟不上信号的变化,但因为看不到微软的底层源码,一直就没有事实的依据作为佐证。

这段时间在分析微软的RefrenceSourceCode时,偶然想起这件事,一查果然是线程池的问题造成的,在微软的所有流式通信处理中,都是对Win32 API的封装处理,并且是一个流处理类的包装,因此在深层次的代码,可以看到SerialStream这个类。

看一下实际的代码:

// ==++==
//
//   Copyright (c) Microsoft Corporation.  All rights reserved.
//
// ==--==
/*=============================================================================
**
** Class: SerialStream
**
** Purpose: Class for enabling low-level [....] and async control over a serial
**        : communications resource.
**
** Date: August, 2002
**
=============================================================================*/

using System;
using System.Collections;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
using System.Globalization;
using System.IO;
using System.Resources;
using System.Runtime;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Remoting.Messaging;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using System.Threading;

using Microsoft.Win32;
using Microsoft.Win32.SafeHandles;


// Notes about the SerialStream:
//  * The stream is always opened via the SerialStream constructor.
//  * Lifetime of the COM port's handle is controlled via a SafeHandle.  Thus, all properties are available
//  * only when the SerialStream is open and not disposed.
//  * Handles to serial communications resources here always:
//  * 1) own the handle
//  * 2) are opened for asynchronous operation
//  * 3) set access at the level of FileAccess.ReadWrite
//  * 4) Allow for reading AND writing
//  * 5) Disallow seeking, since they encapsulate a file of type FILE_TYPE_CHAR

namespace System.IO.Ports
{
    internal sealed class SerialStream : Stream
    {
        const int errorEvents = (int) (SerialError.Frame | SerialError.Overrun |
                                 SerialError.RXOver | SerialError.RXParity | SerialError.TXFull);
        const int receivedEvents = (int) (SerialData.Chars | SerialData.Eof);
        const int pinChangedEvents = (int) (SerialPinChange.Break | SerialPinChange.CDChanged | SerialPinChange.CtsChanged |
                                      SerialPinChange.Ring | SerialPinChange.DsrChanged);

        const int infiniteTimeoutConst = -2;

        // members supporting properties exposed to SerialPort
        private string portName;
        private byte parityReplace = (byte) '?';
        private bool inBreak = false;               // port is initially in non-break state
        private bool isAsync = true;
        private Handshake handshake;
        private bool rtsEnable = false;

        // The internal C# representations of Win32 structures necessary for communication
        // hold most of the internal "fields" maintaining information about the port.
        private UnsafeNativeMethods.DCB dcb;
        private UnsafeNativeMethods.COMMTIMEOUTS commTimeouts;
        private UnsafeNativeMethods.COMSTAT comStat;
        private UnsafeNativeMethods.COMMPROP commProp;

        // internal-use members
        // private const long dsrTimeout = 0L; -- Not used anymore.
        private const int maxDataBits = 8;
        private const int minDataBits = 5;
        internal SafeFileHandle _handle = null;
        internal EventLoopRunner eventRunner;

        private byte[] tempBuf;                 // used to avoid multiple array allocations in ReadByte()

        // called whenever any async i/o operation completes.
        private unsafe static readonly IOCompletionCallback IOCallback = new IOCompletionCallback(SerialStream.AsyncFSCallback);

        // three different events, also wrapped by SerialPort.
        internal event SerialDataReceivedEventHandler   DataReceived;      // called when one character is received.
        internal event SerialPinChangedEventHandler PinChanged;    // called when any of the pin/ring-related triggers occurs
        internal event SerialErrorReceivedEventHandler      ErrorReceived;         // called when any runtime error occurs on the port (frame, overrun, parity, etc.)


        // ----SECTION: inherited properties from Stream class ------------*

        // These six properites are required for SerialStream to inherit from the abstract Stream class.
        // Note four of them are always true or false, and two of them throw exceptions, so these
        // are not usefully queried by applications which know they have a SerialStream, etc...
        public override bool CanRead
        {
            get { return (_handle != null); }
        }

        public override bool CanSeek
        {
            get { return false; }
        }

        public override bool CanTimeout {
            get { return (_handle != null); }
        }
        
        public override bool CanWrite
        {
            get { return (_handle != null); }
        }

        public override long Length
        {
            get { throw new NotSupportedException(SR.GetString(SR.NotSupported_UnseekableStream)); }
        }


        public override long Position
        {
            get { throw new NotSupportedException(SR.GetString(SR.NotSupported_UnseekableStream)); }
            set { throw new NotSupportedException(SR.GetString(SR.NotSupported_UnseekableStream)); }
        }

        // ----- new get-set properties -----------------*

        // Standard port properties, also called from SerialPort
        // BaudRate may not be settable to an arbitrary integer between dwMinBaud and dwMaxBaud,
        // and is limited only by the serial driver.  Typically about twelve values such
        // as Winbase.h's CBR_110 through CBR_256000 are used.
        internal int BaudRate
        {
            //get { return (int) dcb.BaudRate; }
            set
            {
                if (value <= 0 || (value > commProp.dwMaxBaud && commProp.dwMaxBaud > 0))
                {
                    // if no upper bound on baud rate imposed by serial driver, note that argument must be positive
                    if (commProp.dwMaxBaud == 0)
                    {
                        throw new ArgumentOutOfRangeException("baudRate",
                            SR.GetString(SR.ArgumentOutOfRange_NeedPosNum));
                    }
                    else
                    {
                        // otherwise, we can present the bounds on the baud rate for this driver
                        throw new ArgumentOutOfRangeException("baudRate",
                            SR.GetString(SR.ArgumentOutOfRange_Bounds_Lower_Upper, 0, commProp.dwMaxBaud));
                    }
                }
                // Set only if it's different.  Rollback to previous values if setting fails.
                //  This pattern occurs through most of the other properties in this class.
                if(value != dcb.BaudRate)
                {
                    int baudRateOld = (int) dcb.BaudRate;
                    dcb.BaudRate = (uint) value;

                    if (UnsafeNativeMethods.SetCommState(_handle, ref dcb) == false)
                    {
                        dcb.BaudRate = (uint) baudRateOld;
                        InternalResources.WinIOError();
                    }
                }
            }
        }

        public bool BreakState {
            get { return inBreak; }
            set {
                if (value) {
                    if (UnsafeNativeMethods.SetCommBreak(_handle) == false)
                        InternalResources.WinIOError();
                    inBreak = true;
                }
                else {
                    if (UnsafeNativeMethods.ClearCommBreak(_handle) == false)
                        InternalResources.WinIOError();
                    inBreak = false;
                }
            }
        }

        internal int DataBits
        {
            //get  { return (int) dcb.ByteSize; }
            set
            {
                Debug.Assert(!(value < minDataBits || value > maxDataBits), "An invalid value was passed to DataBits");
                if (value != dcb.ByteSize)
                {
                    byte byteSizeOld = dcb.ByteSize;
                    dcb.ByteSize = (byte) value;

                    if (UnsafeNativeMethods.SetCommState(_handle, ref dcb) == false)
                    {
                        dcb.ByteSize = byteSizeOld;
                        InternalResources.WinIOError();
                    }
                }
            }
        }


        internal bool DiscardNull
        {
            //get {   return (GetDcbFlag(NativeMethods.FNULL) == 1);}
            set
            {
                int fNullFlag = GetDcbFlag(NativeMethods.FNULL);
                if(value == true && fNullFlag == 0 || value == false && fNullFlag == 1)
                {
                    int fNullOld = fNullFlag;
                    SetDcbFlag(NativeMethods.FNULL, value ? 1 : 0);

                    if (UnsafeNativeMethods.SetCommState(_handle, ref dcb) == false)
                    {
                        SetDcbFlag(NativeMethods.FNULL, fNullOld);
                        InternalResources.WinIOError();
                    }
                }
            }
        }

        internal bool DtrEnable
        {
            get {   
                int fDtrControl = GetDcbFlag(NativeMethods.FDTRCONTROL);
                
                return (fDtrControl == NativeMethods.DTR_CONTROL_ENABLE);
            }
            set
            {
                // first set the FDTRCONTROL field in the DCB struct
                int fDtrControlOld = GetDcbFlag(NativeMethods.FDTRCONTROL);

                SetDcbFlag(NativeMethods.FDTRCONTROL, value ? NativeMethods.DTR_CONTROL_ENABLE : NativeMethods.DTR_CONTROL_DISABLE);
                if (UnsafeNativeMethods.SetCommState(_handle, ref dcb) == false)
                {
                    SetDcbFlag(NativeMethods.FDTRCONTROL, fDtrControlOld);
                    InternalResources.WinIOError();
                }

                // then set the actual pin 
                if (!UnsafeNativeMethods.EscapeCommFunction(_handle, value ? NativeMethods.SETDTR : NativeMethods.CLRDTR))
                    InternalResources.WinIOError();

            }
        }

        internal Handshake Handshake
        {
            //get  { return handshake; }
            set
            {

                Debug.Assert(!(value < System.IO.Ports.Handshake.None || value > System.IO.Ports.Handshake.RequestToSendXOnXOff), 
                    "An invalid value was passed to Handshake");

                if(value != handshake)
                {
                    // in the DCB, handshake affects the fRtsControl, fOutxCtsFlow, and fInX, fOutX fields,
                    // so we must save everything in that closure before making any changes.
                    Handshake handshakeOld = handshake;
                    int fInOutXOld = GetDcbFlag(NativeMethods.FINX);
                    int fOutxCtsFlowOld = GetDcbFlag(NativeMethods.FOUTXCTSFLOW);
                    int fRtsControlOld = GetDcbFlag(NativeMethods.FRTSCONTROL);

                    handshake = value;
                    int fInXOutXFlag = (handshake == Handshake.XOnXOff || handshake == Handshake.RequestToSendXOnXOff) ? 1 : 0;
                    SetDcbFlag(NativeMethods.FINX, fInXOutXFlag);
                    SetDcbFlag(NativeMethods.FOUTX, fInXOutXFlag);

                    SetDcbFlag(NativeMethods.FOUTXCTSFLOW, (handshake == Handshake.RequestToSend ||
                        handshake == Handshake.RequestToSendXOnXOff) ? 1 : 0);

                    if ((handshake == Handshake.RequestToSend ||
                        handshake == Handshake.RequestToSendXOnXOff))
                    {
                        SetDcbFlag(NativeMethods.FRTSCONTROL, NativeMethods.RTS_CONTROL_HANDSHAKE);
                    }
                    else if (rtsEnable)
                    {
                        SetDcbFlag(NativeMethods.FRTSCONTROL, NativeMethods.RTS_CONTROL_ENABLE);
                    }
                    else {
                        SetDcbFlag(NativeMethods.FRTSCONTROL, NativeMethods.RTS_CONTROL_DISABLE);
                    }                        

                    if (UnsafeNativeMethods.SetCommState(_handle, ref dcb) == false)
                    {
                        handshake = handshakeOld;
                        SetDcbFlag(NativeMethods.FINX, fInOutXOld);
                        SetDcbFlag(NativeMethods.FOUTX, fInOutXOld);
                        SetDcbFlag(NativeMethods.FOUTXCTSFLOW, fOutxCtsFlowOld);
                        SetDcbFlag(NativeMethods.FRTSCONTROL, fRtsControlOld);
                        InternalResources.WinIOError();
                    }

                }
            }
        }

        internal bool IsOpen {
            get {
                return _handle != null && !eventRunner.ShutdownLoop;
            }
        }
        
        internal Parity Parity 
        { 
            //get     {   return (Parity) dcb.Parity;     } 
            set 
            { 
                Debug.Assert(!(value < Parity.None || value > Parity.Space), "An invalid value was passed to Parity");

                if((byte) value != dcb.Parity)
                {
                    byte parityOld = dcb.Parity;

                    // in the DCB structure, the parity setting also potentially effects:
                    // fParity, fErrorChar, ErrorChar
                    // so these must be saved as well.
                    int fParityOld = GetDcbFlag(NativeMethods.FPARITY);
                    byte ErrorCharOld = dcb.ErrorChar;
                    int fErrorCharOld = GetDcbFlag(NativeMethods.FERRORCHAR);
                    dcb.Parity = (byte) value;

                    int parityFlag = (dcb.Parity == (byte) Parity.None) ? 0 : 1;
                    SetDcbFlag(NativeMethods.FPARITY, parityFlag);
                    if (parityFlag == 1)
                    {
                        SetDcbFlag(NativeMethods.FERRORCHAR, (parityReplace != '\0') ? 1 : 0);
                        dcb.ErrorChar = parityReplace;
                    }
                    else
                    {
                        SetDcbFlag(NativeMethods.FERRORCHAR, 0);
                        dcb.ErrorChar = (byte) '\0';
                    }
                    if (UnsafeNativeMethods.SetCommState(_handle, ref dcb) == false)
                    {
                        dcb.Parity = parityOld;
                        SetDcbFlag(NativeMethods.FPARITY, fParityOld);

                        dcb.ErrorChar = ErrorCharOld;
                        SetDcbFlag(NativeMethods.FERRORCHAR, fErrorCharOld);

                        InternalResources.WinIOError();
                    }
                }
            }
        }

        // ParityReplace is the eight-bit character which replaces any bytes which
        // ParityReplace affects the equivalent field in the DCB structure: ErrorChar, and
        // the DCB flag fErrorChar.
        internal byte ParityReplace
        {
            //get {   return parityReplace; }
            set
            {
                if(value != parityReplace)
                {
                    byte parityReplaceOld = parityReplace;
                    byte errorCharOld = dcb.ErrorChar;
                    int fErrorCharOld = GetDcbFlag(NativeMethods.FERRORCHAR);

                    parityReplace = value;
                    if (GetDcbFlag(NativeMethods.FPARITY) == 1)
                    {
                        SetDcbFlag(NativeMethods.FERRORCHAR, (parityReplace != '\0')? 1 : 0);
                        dcb.ErrorChar = parityReplace;
                    }
                    else
                    {
                        SetDcbFlag(NativeMethods.FERRORCHAR, 0);
                        dcb.ErrorChar = (byte) '\0';
                    }


                    if (UnsafeNativeMethods.SetCommState(_handle, ref dcb) == false)
                    {
                        parityReplace = parityReplaceOld;
                        SetDcbFlag(NativeMethods.FERRORCHAR, fErrorCharOld);
                        dcb.ErrorChar = errorCharOld;
                        InternalResources.WinIOError();
                    }
                }
            }
        }

        // Timeouts are considered to be TOTAL time for the Read/Write operation and to be in milliseconds.
        // Timeouts are translated into DCB structure as follows:
        // Desired timeout      =>  ReadTotalTimeoutConstant    ReadTotalTimeoutMultiplier  ReadIntervalTimeout
        //  0                                   0                           0               MAXDWORD
        //  0 < n < infinity                    n                       MAXDWORD            MAXDWORD
        // infinity                             infiniteTimeoutConst    MAXDWORD            MAXDWORD
        //
        // rationale for "infinity": There does not exist in the COMMTIMEOUTS structure a way to
        // *wait indefinitely for any byte, return when found*.  Instead, if we set ReadTimeout
        // to infinity, SerialStream's EndRead loops if infiniteTimeoutConst mills have elapsed
        // without a byte received.  Note that this is approximately 24 days, so essentially
        // most practical purposes effectively equate 24 days with an infinite amount of time
        // on a serial port connection.
        public override int ReadTimeout
        {
            get
            {
                int constant = commTimeouts.ReadTotalTimeoutConstant;

                if (constant == infiniteTimeoutConst) return SerialPort.InfiniteTimeout;
                else return constant;
            }
            set
            {
                if (value < 0 && value != SerialPort.InfiniteTimeout)
                    throw new ArgumentOutOfRangeException("ReadTimeout", SR.GetString(SR.ArgumentOutOfRange_Timeout));
                if (_handle == null) InternalResources.FileNotOpen();

                int oldReadConstant = commTimeouts.ReadTotalTimeoutConstant;
                int oldReadInterval = commTimeouts.ReadIntervalTimeout;
                int oldReadMultipler = commTimeouts.ReadTotalTimeoutMultiplier;

                // NOTE: this logic should match what is in the constructor
                if (value == 0) {
                    commTimeouts.ReadTotalTimeoutConstant   = 0;
                    commTimeouts.ReadTotalTimeoutMultiplier = 0;
                    commTimeouts.ReadIntervalTimeout        = NativeMethods.MAXDWORD;
                } else if (value == SerialPort.InfiniteTimeout) {
                    // SetCommTimeouts doesn't like a value of -1 for some reason, so
                    // we'll use -2(infiniteTimeoutConst) to represent infinite. 
                    commTimeouts.ReadTotalTimeoutConstant   = infiniteTimeoutConst;
                    commTimeouts.ReadTotalTimeoutMultiplier = NativeMethods.MAXDWORD;
                    commTimeouts.ReadIntervalTimeout        = NativeMethods.MAXDWORD;
                } else {
                    commTimeouts.ReadTotalTimeoutConstant   = value;
                    commTimeouts.ReadTotalTimeoutMultiplier = NativeMethods.MAXDWORD;
                    commTimeouts.ReadIntervalTimeout        = NativeMethods.MAXDWORD;
                }

                if (UnsafeNativeMethods.SetCommTimeouts(_handle, ref commTimeouts) == false)
                {
                    commTimeouts.ReadTotalTimeoutConstant = oldReadConstant;
                    commTimeouts.ReadTotalTimeoutMultiplier = oldReadMultipler;
                    commTimeouts.ReadIntervalTimeout = oldReadInterval;
                    InternalResources.WinIOError();
                }
            }
        }

        internal bool RtsEnable
        {
            get { 
                int fRtsControl = GetDcbFlag(NativeMethods.FRTSCONTROL);
                if (fRtsControl == NativeMethods.RTS_CONTROL_HANDSHAKE)
                    throw new InvalidOperationException(SR.GetString(SR.CantSetRtsWithHandshaking));

                return (fRtsControl == NativeMethods.RTS_CONTROL_ENABLE);
            }
            set
            {
                if ((handshake == Handshake.RequestToSend || handshake == Handshake.RequestToSendXOnXOff))
                    throw new InvalidOperationException(SR.GetString(SR.CantSetRtsWithHandshaking));

                if (value != rtsEnable) {
                    int fRtsControlOld = GetDcbFlag(NativeMethods.FRTSCONTROL);

                    rtsEnable = value;
                    if(value)
                        SetDcbFlag(NativeMethods.FRTSCONTROL, NativeMethods.RTS_CONTROL_ENABLE);
                    else
                        SetDcbFlag(NativeMethods.FRTSCONTROL, NativeMethods.RTS_CONTROL_DISABLE);

                    if (UnsafeNativeMethods.SetCommState(_handle, ref dcb) == false)
                    {
                        SetDcbFlag(NativeMethods.FRTSCONTROL, fRtsControlOld);
                        // set it back to the old value on a failure
                        rtsEnable = !rtsEnable;
                        InternalResources.WinIOError();
                    }

                    if (!UnsafeNativeMethods.EscapeCommFunction(_handle, value ? NativeMethods.SETRTS : NativeMethods.CLRRTS))
                        InternalResources.WinIOError();
                }
            }
        }

        // StopBits represented in C# as StopBits enum type and in Win32 as an integer 1, 2, or 3.
        internal StopBits StopBits
        {
            /*get
            {
                switch(dcb.StopBits)
                {
                    case NativeMethods.ONESTOPBIT:
                        return StopBits.One;
                    case NativeMethods.ONE5STOPBITS:
                        return StopBits.OnePointFive;
                    case NativeMethods.TWOSTOPBITS:
                        return StopBits.Two;
                    default:
                        Debug.Assert(true, "Invalid Stopbits value " + dcb.StopBits);
                        return StopBits.One;

                }
            }
            */
            set
            {
                Debug.Assert(!(value < StopBits.One || value > StopBits.OnePointFive), "An invalid value was passed to StopBits");

                byte nativeValue = 0;
                if (value == StopBits.One) nativeValue = (byte) NativeMethods.ONESTOPBIT;
                else if (value == StopBits.OnePointFive) nativeValue = (byte) NativeMethods.ONE5STOPBITS;
                else nativeValue = (byte) NativeMethods.TWOSTOPBITS;

                if(nativeValue != dcb.StopBits)
                {
                    byte stopBitsOld = dcb.StopBits;
                    dcb.StopBits = nativeValue;

                    if (UnsafeNativeMethods.SetCommState(_handle, ref dcb) == false)
                    {
                        dcb.StopBits = stopBitsOld;
                        InternalResources.WinIOError();
                    }
                }
            }
        }

        // note: WriteTimeout must be either SerialPort.InfiniteTimeout or POSITIVE.
        // a timeout of zero implies that every Write call throws an exception.
        public override int WriteTimeout
        {
            get
            {
                int timeout = commTimeouts.WriteTotalTimeoutConstant;
                return (timeout == 0) ? SerialPort.InfiniteTimeout : timeout;
            }
            set
            {
                if (value <= 0 && value != SerialPort.InfiniteTimeout)
                    throw new ArgumentOutOfRangeException("WriteTimeout", SR.GetString(SR.ArgumentOutOfRange_WriteTimeout));
                if (_handle == null) InternalResources.FileNotOpen();

                int oldWriteConstant = commTimeouts.WriteTotalTimeoutConstant;
                commTimeouts.WriteTotalTimeoutConstant = ((value == SerialPort.InfiniteTimeout) ? 0 : value);

                if (UnsafeNativeMethods.SetCommTimeouts(_handle, ref commTimeouts) == false)
                {
                    commTimeouts.WriteTotalTimeoutConstant = oldWriteConstant;
                    InternalResources.WinIOError();
                }
            }
        }


        // CDHolding, CtsHolding, DsrHolding query the current state of each of the carrier, the CTS pin,
        // and the DSR pin, respectively. Read-only.
        // All will throw exceptions if the port is not open.
        internal bool CDHolding
        {
            get
            {
                int pinStatus = 0;
                if (UnsafeNativeMethods.GetCommModemStatus(_handle, ref pinStatus) == false)
                    InternalResources.WinIOError();

                return (NativeMethods.MS_RLSD_ON & pinStatus) != 0;
            }
        }


        internal bool CtsHolding
        {
            get
            {
                int pinStatus = 0;
                if (UnsafeNativeMethods.GetCommModemStatus(_handle, ref pinStatus) == false)
                    InternalResources.WinIOError();
                return (NativeMethods.MS_CTS_ON & pinStatus) != 0;
            }

        }

        internal bool DsrHolding
        {
            get
            {
                int pinStatus = 0;
                if (UnsafeNativeMethods.GetCommModemStatus(_handle, ref pinStatus) == false)
                    InternalResources.WinIOError();

                return (NativeMethods.MS_DSR_ON & pinStatus) != 0;
            }
        }


        // Fills comStat structure from an unmanaged function
        // to determine the number of bytes waiting in the serial driver's internal receive buffer.
        internal int BytesToRead {
            get
            {
                int errorCode = 0; // "ref" arguments need to have values, as opposed to "out" arguments
                if (UnsafeNativeMethods.ClearCommError(_handle, ref errorCode, ref comStat)  == false)
                {
                    InternalResources.WinIOError();
                }
                return (int) comStat.cbInQue;
            }
        }

        // Fills comStat structure from an unmanaged function
        // to determine the number of bytes waiting in the serial driver's internal transmit buffer.
        internal int BytesToWrite {
            get
            {
                int errorCode = 0; // "ref" arguments need to be set before method invocation, as opposed to "out" arguments
                if (UnsafeNativeMethods.ClearCommError(_handle, ref errorCode, ref comStat)  == false)
                    InternalResources.WinIOError();
                return (int) comStat.cbOutQue;

            }
        }

        // -----------SECTION: constructor --------------------------*

        // this method is used by SerialPort upon SerialStream's creation
        [ResourceExposure(ResourceScope.Machine)]
        [ResourceConsumption(ResourceScope.Machine)]
        internal SerialStream(string portName, int baudRate, Parity parity, int dataBits, StopBits stopBits, int readTimeout, int writeTimeout, Handshake handshake,
            bool dtrEnable, bool rtsEnable, bool discardNull, byte parityReplace)
        {

            int flags = UnsafeNativeMethods.FILE_FLAG_OVERLAPPED;
            // disable async on win9x
            if (Environment.OSVersion.Platform == PlatformID.Win32Windows) {
                flags = UnsafeNativeMethods.FILE_ATTRIBUTE_NORMAL;
                isAsync = false;
            }

            if ((portName == null) || !portName.StartsWith("COM", StringComparison.OrdinalIgnoreCase)) 
                throw new ArgumentException(SR.GetString(SR.Arg_InvalidSerialPort), "portName");

            //Error checking done in SerialPort.

            SafeFileHandle tempHandle = UnsafeNativeMethods.CreateFile("\\\\.\\" + portName,
                NativeMethods.GENERIC_READ | NativeMethods.GENERIC_WRITE,
                0,    // comm devices must be opened w/exclusive-access
                IntPtr.Zero, // no security attributes
                UnsafeNativeMethods.OPEN_EXISTING, // comm devices must use OPEN_EXISTING
                flags,
                IntPtr.Zero  // hTemplate must be NULL for comm devices
                );

            if (tempHandle.IsInvalid)
            {
                InternalResources.WinIOError(portName);
            }

            try {
                int fileType = UnsafeNativeMethods.GetFileType(tempHandle);

                // Allowing FILE_TYPE_UNKNOWN for legitimate serial device such as USB to serial adapter device 
                if ((fileType != UnsafeNativeMethods.FILE_TYPE_CHAR) && (fileType != UnsafeNativeMethods.FILE_TYPE_UNKNOWN))
                    throw new ArgumentException(SR.GetString(SR.Arg_InvalidSerialPort), "portName");

                _handle = tempHandle;

                // set properties of the stream that exist as members in SerialStream
                this.portName = portName;
                this.handshake = handshake;
                this.parityReplace = parityReplace;

                tempBuf = new byte[1];          // used in ReadByte()

                // Fill COMMPROPERTIES struct, which has our maximum allowed baud rate.
                // Call a serial specific API such as GetCommModemStatus which would fail
                // in case the device is not a legitimate serial device. For instance, 
                // some illegal FILE_TYPE_UNKNOWN device (or) "LPT1" on Win9x 
                // trying to pass for serial will be caught here. GetCommProperties works
                // fine for "LPT1" on Win9x, so that alone can't be relied here to
                // detect non serial devices.
                               
                commProp = new UnsafeNativeMethods.COMMPROP();
                int pinStatus = 0;
                
                if (!UnsafeNativeMethods.GetCommProperties(_handle, ref commProp) 
                    || !UnsafeNativeMethods.GetCommModemStatus(_handle, ref pinStatus))
                {
                    // If the portName they have passed in is a FILE_TYPE_CHAR but not a serial port,
                    // for example "LPT1", this API will fail.  For this reason we handle the error message specially. 
                    int errorCode = Marshal.GetLastWin32Error();
                    if ((errorCode == NativeMethods.ERROR_INVALID_PARAMETER) || (errorCode == NativeMethods.ERROR_INVALID_HANDLE))
                        throw new ArgumentException(SR.GetString(SR.Arg_InvalidSerialPortExtended), "portName");
                    else 
                        InternalResources.WinIOError(errorCode, string.Empty);
                }
                if (commProp.dwMaxBaud != 0 && baudRate > commProp.dwMaxBaud)
                    throw new ArgumentOutOfRangeException("baudRate", SR.GetString(SR.Max_Baud, commProp.dwMaxBaud));


                comStat = new UnsafeNativeMethods.COMSTAT();
                // create internal DCB structure, initialize according to Platform SDK
                // standard: ms-help://MS.MSNDNQTR.2002APR.1003/hardware/commun_965u.htm
                dcb = new UnsafeNativeMethods.DCB();

                // set constant properties of the DCB
                InitializeDCB(baudRate, parity, dataBits, stopBits, discardNull);

                this.DtrEnable = dtrEnable;

                // query and cache the initial RtsEnable value 
                // so that set_RtsEnable can do the (value != rtsEnable) optimization
                this.rtsEnable = (GetDcbFlag(NativeMethods.FRTSCONTROL) == NativeMethods.RTS_CONTROL_ENABLE);

                // now set this.RtsEnable to the specified value.
                // Handshake takes precedence, this will be a nop if 
                // handshake is either RequestToSend or RequestToSendXOnXOff 
                if ((handshake != Handshake.RequestToSend && handshake != Handshake.RequestToSendXOnXOff))
                    this.RtsEnable = rtsEnable;

                // NOTE: this logic should match what is in the ReadTimeout property
                if (readTimeout == 0) {
                    commTimeouts.ReadTotalTimeoutConstant   = 0;
                    commTimeouts.ReadTotalTimeoutMultiplier = 0;
                    commTimeouts.ReadIntervalTimeout        = NativeMethods.MAXDWORD;
                } else if (readTimeout == SerialPort.InfiniteTimeout) {
                    // SetCommTimeouts doesn't like a value of -1 for some reason, so
                    // we'll use -2(infiniteTimeoutConst) to represent infinite. 
                    commTimeouts.ReadTotalTimeoutConstant   = infiniteTimeoutConst;
                    commTimeouts.ReadTotalTimeoutMultiplier = NativeMethods.MAXDWORD;
                    commTimeouts.ReadIntervalTimeout        = NativeMethods.MAXDWORD;
                } else {
                    commTimeouts.ReadTotalTimeoutConstant   = readTimeout;
                    commTimeouts.ReadTotalTimeoutMultiplier = NativeMethods.MAXDWORD;
                    commTimeouts.ReadIntervalTimeout        = NativeMethods.MAXDWORD;
                }

                commTimeouts.WriteTotalTimeoutMultiplier    = 0;
                commTimeouts.WriteTotalTimeoutConstant      = ((writeTimeout == SerialPort.InfiniteTimeout) ? 0 : writeTimeout);

                // set unmanaged timeout structure
                if (UnsafeNativeMethods.SetCommTimeouts(_handle, ref commTimeouts) == false)
                {
                    InternalResources.WinIOError();
                }

                if (isAsync) {
                    if (!ThreadPool.BindHandle(_handle))
                    {
                        throw new IOException(SR.GetString(SR.IO_BindHandleFailed));
                    }
                }

                // monitor all events except TXEMPTY
                UnsafeNativeMethods.SetCommMask(_handle, NativeMethods.ALL_EVENTS);

                // prep. for starting event cycle.
                eventRunner = new EventLoopRunner(this);
                Thread eventLoopThread = new Thread(new ThreadStart(eventRunner.WaitForCommEvent));
                eventLoopThread.IsBackground = true;
                eventLoopThread.Start();
                
            }
            catch  {
                // if there are any exceptions after the call to CreateFile, we need to be sure to close the
                // handle before we let them continue up.
                tempHandle.Close();
                _handle = null;
                throw;
            }
        }

        ~SerialStream()
        {
            Dispose(false);
        }

        protected override void Dispose(bool disposing)
        {
            // Signal the other side that we're closing.  Should do regardless of whether we've called
            // Close() or not Dispose() 
            if (_handle != null && !_handle.IsInvalid) {
                try {

                    eventRunner.endEventLoop = true;

                    Thread.MemoryBarrier();

                    bool skipSPAccess = false;

                    // turn off all events and signal WaitCommEvent
                    UnsafeNativeMethods.SetCommMask(_handle, 0);
                    if (!UnsafeNativeMethods.EscapeCommFunction(_handle, NativeMethods.CLRDTR)) 
                    {
                        int hr = Marshal.GetLastWin32Error();
 
                        // access denied can happen if USB is yanked out. If that happens, we
                        // want to at least allow finalize to succeed and clean up everything 
                        // we can. To achieve this, we need to avoid further attempts to access
                        // the SerialPort.  A customer also reported seeing ERROR_BAD_COMMAND here.
                        // Do not throw an exception on the finalizer thread - that's just rude,
                        // since apps can't catch it and we may tear down the app.
                        if ((hr == NativeMethods.ERROR_ACCESS_DENIED || hr == NativeMethods.ERROR_BAD_COMMAND) && !disposing) {
                            skipSPAccess = true;
                        }
                        else {
                            // should not happen
                            Contract.Assert(false, String.Format("Unexpected error code from EscapeCommFunction in SerialPort.Dispose(bool)  Error code: 0x{0:x}", (uint)hr));
                            // Do not throw an exception from the finalizer here.
                            if (disposing)
                                InternalResources.WinIOError();
                        }
                    }

                    if (!skipSPAccess && !_handle.IsClosed) {
                        Flush();
                    }

                    eventRunner.waitCommEventWaitHandle.Set();

                    if (!skipSPAccess) {
                        DiscardInBuffer();
                        DiscardOutBuffer();
                    }

                    if (disposing && eventRunner != null) {
                        // now we need to wait for the event loop to tell us it's done.  Without this we could get into a ---- where the
                        // event loop kept the port open even after Dispose ended.
                        eventRunner.eventLoopEndedSignal.WaitOne();
                        eventRunner.eventLoopEndedSignal.Close();
                        eventRunner.waitCommEventWaitHandle.Close();
                    }
                }
                finally {
                    // If we are disposing synchronize closing with raising SerialPort events
                    if (disposing) {
                        lock (this) {
                            _handle.Close();
                            _handle = null;
                        }
                    }
                    else {
                        _handle.Close();
                        _handle = null;
                    }
                    base.Dispose(disposing);
                }

            }
        }

        // -----SECTION: all public methods ------------------*

        // User-accessible async read method.  Returns SerialStreamAsyncResult : IAsyncResult
        [HostProtection(ExternalThreading=true)]
        public override IAsyncResult BeginRead(byte[] array, int offset,int numBytes, AsyncCallback userCallback, object stateObject)
        {
            if (array==null)
                throw new ArgumentNullException("array");
            if (offset < 0)
                throw new ArgumentOutOfRangeException("offset", SR.GetString(SR.ArgumentOutOfRange_NeedNonNegNumRequired));
            if (numBytes < 0)
                throw new ArgumentOutOfRangeException("numBytes", SR.GetString(SR.ArgumentOutOfRange_NeedNonNegNumRequired));
            if (array.Length - offset < numBytes)
                throw new ArgumentException(SR.GetString(SR.Argument_InvalidOffLen));
            if (_handle == null) InternalResources.FileNotOpen();

            int oldtimeout = ReadTimeout;
            ReadTimeout = SerialPort.InfiniteTimeout;
            IAsyncResult result;
            try {
                if (!isAsync)
                    result = base.BeginRead(array, offset, numBytes, userCallback, stateObject);
                else
                    result = BeginReadCore(array, offset, numBytes, userCallback, stateObject);

            }
            finally {
                ReadTimeout = oldtimeout;
            }
            return result;
        }

        // User-accessible async write method.  Returns SerialStreamAsyncResult : IAsyncResult
        // Throws an exception if port is in break state.
        [HostProtection(ExternalThreading=true)]
        public override IAsyncResult BeginWrite(byte[] array, int offset, int numBytes,
            AsyncCallback userCallback, object stateObject)
        {
            if (inBreak)
                throw new InvalidOperationException(SR.GetString(SR.In_Break_State));
            if (array==null)
                throw new ArgumentNullException("array");
            if (offset < 0)
                throw new ArgumentOutOfRangeException("offset", SR.GetString(SR.ArgumentOutOfRange_NeedNonNegNumRequired));
            if (numBytes < 0)
                throw new ArgumentOutOfRangeException("numBytes", SR.GetString(SR.ArgumentOutOfRange_NeedNonNegNumRequired));
            if (array.Length - offset < numBytes)
                throw new ArgumentException(SR.GetString(SR.Argument_InvalidOffLen));
            if (_handle == null) InternalResources.FileNotOpen();

            int oldtimeout = WriteTimeout;
            WriteTimeout = SerialPort.InfiniteTimeout;
            IAsyncResult result;
            try {
                if (!isAsync)
                    result = base.BeginWrite(array, offset, numBytes, userCallback, stateObject); 
                else
                    result = BeginWriteCore(array, offset, numBytes, userCallback, stateObject);
            }
            finally {
                WriteTimeout = oldtimeout;
            }
            return result;
        }

        // Uses Win32 method to dump out the receive buffer; analagous to MSComm's "InBufferCount = 0"
        internal void DiscardInBuffer()
        {
            
            if (UnsafeNativeMethods.PurgeComm(_handle, NativeMethods.PURGE_RXCLEAR | NativeMethods.PURGE_RXABORT) == false)
                InternalResources.WinIOError();
        }

        // Uses Win32 method to dump out the xmit buffer; analagous to MSComm's "OutBufferCount = 0"
        internal void DiscardOutBuffer()
        {
            if (UnsafeNativeMethods.PurgeComm(_handle, NativeMethods.PURGE_TXCLEAR | NativeMethods.PURGE_TXABORT) == false)
                InternalResources.WinIOError();
        }

        // Async companion to BeginRead.
        // Note, assumed IAsyncResult argument is of derived type SerialStreamAsyncResult,
        // and throws an exception if untrue.
        public unsafe override int EndRead(IAsyncResult asyncResult)
        {
            if (!isAsync) 
                return base.EndRead(asyncResult);

            if (asyncResult==null)
                throw new ArgumentNullException("asyncResult");

            SerialStreamAsyncResult afsar = asyncResult as SerialStreamAsyncResult;
            if (afsar==null || afsar._isWrite)
                InternalResources.WrongAsyncResult();

            // This sidesteps race conditions, avoids memory corruption after freeing the
            // NativeOverlapped class or GCHandle twice.
            if (1 == Interlocked.CompareExchange(ref afsar._EndXxxCalled, 1, 0))
                InternalResources.EndReadCalledTwice();

            bool failed = false;

            // Obtain the WaitHandle, but don't use public property in case we
            // delay initialize the manual reset event in the future.
            WaitHandle wh = afsar._waitHandle;
            if (wh != null)
            {
                // We must block to ensure that AsyncFSCallback has completed,
                // and we should close the WaitHandle in here.  
                try {
                    wh.WaitOne();
                    Debug.Assert(afsar._isComplete == true, "SerialStream::EndRead - AsyncFSCallback didn't set _isComplete to true!");
                
                    // InfiniteTimeout is not something native to the underlying serial device, 
                    // we specify the timeout to be a very large value (MAXWORD-1) to achieve 
                    // an infinite timeout illusion. 

                    // I'm not sure what we can do here after an asyn operation with infinite 
                    // timeout returns with no data. From a purist point of view we should 
                    // somehow restart the read operation but we are not in a position to do so
                    // (and frankly that may not necessarily be the right thing to do here) 
                    // I think the best option in this (almost impossible to run into) situation 
                    // is to throw some sort of IOException.

                    if ((afsar._numBytes == 0) && (ReadTimeout == SerialPort.InfiniteTimeout) && (afsar._errorCode == 0))
                      failed = true;
                }
                finally {
                    wh.Close();
                }
            }

            // Free memory, GC handles.
            NativeOverlapped* overlappedPtr = afsar._overlapped;
            if (overlappedPtr != null)
                Overlapped.Free(overlappedPtr);

            // Check for non-timeout errors during the read.
            if (afsar._errorCode != 0) 
                InternalResources.WinIOError(afsar._errorCode, portName);

            if (failed)
                throw new IOException(SR.GetString(SR.IO_OperationAborted));
            
            return afsar._numBytes;
        }

        // Async companion to BeginWrite.
        // Note, assumed IAsyncResult argument is of derived type SerialStreamAsyncResult,
        // and throws an exception if untrue.
        // Also fails if called in port's break state.
        public unsafe override void EndWrite(IAsyncResult asyncResult) {
            if (!isAsync) {
                base.EndWrite(asyncResult);
                return;
            }

            if (inBreak)
                throw new InvalidOperationException(SR.GetString(SR.In_Break_State));
            if (asyncResult==null)
                throw new ArgumentNullException("asyncResult");

            SerialStreamAsyncResult afsar = asyncResult as SerialStreamAsyncResult;
            if (afsar==null || !afsar._isWrite)
                InternalResources.WrongAsyncResult();

            // This sidesteps race conditions, avoids memory corruption after freeing the
            // NativeOverlapped class or GCHandle twice.
            if (1 == Interlocked.CompareExchange(ref afsar._EndXxxCalled, 1, 0))
                InternalResources.EndWriteCalledTwice();

            // Obtain the WaitHandle, but don't use public property in case we
            // delay initialize the manual reset event in the future.
            WaitHandle wh = afsar._waitHandle;
            if (wh != null) 
            {
                // We must block to ensure that AsyncFSCallback has completed,
                // and we should close the WaitHandle in here.  
                try {
                    wh.WaitOne();
                    Debug.Assert(afsar._isComplete == true, "SerialStream::EndWrite - AsyncFSCallback didn't set _isComplete to true!");
                }
                finally {
                    wh.Close();
                }
            }

            // Free memory, GC handles.
            NativeOverlapped* overlappedPtr = afsar._overlapped;
            if (overlappedPtr != null)
                Overlapped.Free(overlappedPtr);

            // Now check for any error during the write.
            if (afsar._errorCode != 0)
                InternalResources.WinIOError(afsar._errorCode, portName);

            // Number of bytes written is afsar._numBytes.
        }

        // Flush dumps the contents of the serial driver's internal read and write buffers.
        // We actually expose the functionality for each, but fulfilling Stream's contract
        // requires a Flush() method.  Fails if handle closed.
        // Note: Serial driver's write buffer is *already* attempting to write it, so we can only wait until it finishes.
        public override void Flush() 
        { 
            if (_handle == null) throw new ObjectDisposedException(SR.GetString(SR.Port_not_open));
            UnsafeNativeMethods.FlushFileBuffers(_handle);
        }

        // Blocking read operation, returning the number of bytes read from the stream.

        public override int Read([In, Out] byte[] array, int offset, int count)
        {
            return Read(array, offset, count, ReadTimeout);
        }

        internal unsafe int Read([In, Out] byte[] array, int offset, int count, int timeout)
        {
            if (array==null)
                throw new ArgumentNullException("array", SR.GetString(SR.ArgumentNull_Buffer));
            if (offset < 0)
                throw new ArgumentOutOfRangeException("offset", SR.GetString(SR.ArgumentOutOfRange_NeedNonNegNumRequired));
            if (count < 0)
                throw new ArgumentOutOfRangeException("count", SR.GetString(SR.ArgumentOutOfRange_NeedNonNegNumRequired));
            if (array.Length - offset < count)
                throw new ArgumentException(SR.GetString(SR.Argument_InvalidOffLen));
            if (count == 0) return 0; // return immediately if no bytes requested; no need for overhead.

            Debug.Assert(timeout == SerialPort.InfiniteTimeout || timeout >= 0, "Serial Stream Read - called with timeout " + timeout);

            // Check to see we have no handle-related error, since the port's always supposed to be open.
            if (_handle == null) InternalResources.FileNotOpen();

            int numBytes = 0;
            int hr;
            if (isAsync) {
                IAsyncResult result = BeginReadCore(array, offset, count, null, null);
                numBytes = EndRead(result);
            }
            else {
                numBytes = ReadFileNative(array, offset, count, null, out hr);
                if (numBytes == -1) {
                    InternalResources.WinIOError();
                }
            }

            if (numBytes == 0)
                throw new TimeoutException();

            return numBytes;
        }

        public override int ReadByte()
        {
            return ReadByte(ReadTimeout);
        }

        internal unsafe int ReadByte(int timeout)
        {
            if (_handle == null) InternalResources.FileNotOpen();

            int numBytes = 0;
            int hr;
            if (isAsync) {
                IAsyncResult result = BeginReadCore(tempBuf, 0, 1, null, null);
                numBytes = EndRead(result);
            }
            else {
                numBytes = ReadFileNative(tempBuf, 0, 1, null, out hr);
                if (numBytes == -1) {
                    InternalResources.WinIOError();
                }
            }

            if (numBytes == 0)
                throw new TimeoutException();
            else
                return tempBuf[0];
        }

        public override long Seek(long offset, SeekOrigin origin)
        {
            throw new NotSupportedException(SR.GetString(SR.NotSupported_UnseekableStream));
        }

        public override void SetLength(long value)
        {
            throw new NotSupportedException(SR.GetString(SR.NotSupported_UnseekableStream));
        }

        internal void SetBufferSizes(int readBufferSize, int writeBufferSize) {
            if (_handle == null) InternalResources.FileNotOpen();

            if (!UnsafeNativeMethods.SetupComm(_handle, readBufferSize, writeBufferSize))
                InternalResources.WinIOError();
        }

        public override void Write(byte[] array, int offset, int count)
        {
            Write(array, offset, count, WriteTimeout);
        }
        
        internal unsafe void Write(byte[] array, int offset, int count, int timeout)
        {

            if (inBreak)
                throw new InvalidOperationException(SR.GetString(SR.In_Break_State));
            if (array==null)
                throw new ArgumentNullException("buffer", SR.GetString(SR.ArgumentNull_Array));
            if (offset < 0)
                throw new ArgumentOutOfRangeException("offset", SR.GetString(SR.ArgumentOutOfRange_NeedPosNum));
            if (count < 0)
                throw new ArgumentOutOfRangeException("count", SR.GetString(SR.ArgumentOutOfRange_NeedPosNum));
            if (count == 0) return; // no need to expend overhead in creating asyncResult, etc.
            if (array.Length - offset < count)
                throw new ArgumentException("count",SR.GetString(SR.ArgumentOutOfRange_OffsetOut));
            Debug.Assert(timeout == SerialPort.InfiniteTimeout || timeout >= 0, "Serial Stream Write - write timeout is " + timeout);

            // check for open handle, though the port is always supposed to be open
            if (_handle == null) InternalResources.FileNotOpen();

            int numBytes;
            int hr;
            if (isAsync) {
                IAsyncResult result = BeginWriteCore(array, offset, count, null, null);
                EndWrite(result);

                SerialStreamAsyncResult afsar = result as SerialStreamAsyncResult;
                Debug.Assert(afsar != null, "afsar should be a SerialStreamAsyncResult and should not be null");
                numBytes = afsar._numBytes;
            } 
            else {
                numBytes = WriteFileNative(array, offset, count, null, out hr);
                if (numBytes == -1) {

                    // This is how writes timeout on Win9x. 
                    if (hr == NativeMethods.ERROR_COUNTER_TIMEOUT)
                        throw new TimeoutException(SR.GetString(SR.Write_timed_out));

                    InternalResources.WinIOError();
                }
            }
            
            if (numBytes == 0)
                throw new TimeoutException(SR.GetString(SR.Write_timed_out));

        }

        // use default timeout as argument to WriteByte override with timeout arg
        public override void WriteByte(byte value)
        {
            WriteByte(value, WriteTimeout);
        }

        internal unsafe void WriteByte(byte value, int timeout)
        {
            if (inBreak)
                throw new InvalidOperationException(SR.GetString(SR.In_Break_State));

            if (_handle == null) InternalResources.FileNotOpen();
            tempBuf[0] = value;


            int numBytes;
            int hr;
            if (isAsync) {
                IAsyncResult result = BeginWriteCore(tempBuf, 0, 1, null, null);
                EndWrite(result);

                SerialStreamAsyncResult afsar = result as SerialStreamAsyncResult;
                Debug.Assert(afsar != null, "afsar should be a SerialStreamAsyncResult and should not be null");
                numBytes = afsar._numBytes;
            }
            else {
                numBytes = WriteFileNative(tempBuf, 0, 1, null, out hr);
                if (numBytes == -1) {
                    // This is how writes timeout on Win9x. 
                    if (Marshal.GetLastWin32Error() == NativeMethods.ERROR_COUNTER_TIMEOUT)
                        throw new TimeoutException(SR.GetString(SR.Write_timed_out));

                    InternalResources.WinIOError();
                }
            }
            
            if (numBytes == 0)
                throw new TimeoutException(SR.GetString(SR.Write_timed_out));

            return;
        }



        // --------SUBSECTION: internal-use methods ----------------------*
        // ------ internal DCB-supporting methods ------- *

        // Initializes unmananged DCB struct, to be called after opening communications resource.
        // assumes we have already: baudRate, parity, dataBits, stopBits
        // should only be called in SerialStream(...)
        private void InitializeDCB(int baudRate, Parity parity, int dataBits, StopBits stopBits, bool discardNull)
        {

            // first get the current dcb structure setup
            if (UnsafeNativeMethods.GetCommState(_handle, ref dcb) == false)
            {
                InternalResources.WinIOError();
            }
            dcb.DCBlength = (uint) System.Runtime.InteropServices.Marshal.SizeOf(dcb);

            // set parameterized properties
            dcb.BaudRate = (uint) baudRate;
            dcb.ByteSize = (byte) dataBits;


            switch (stopBits)
            {
                case StopBits.One:
                    dcb.StopBits = NativeMethods.ONESTOPBIT;
                    break;
                case StopBits.OnePointFive:
                    dcb.StopBits = NativeMethods.ONE5STOPBITS;
                    break;
                case StopBits.Two:
                    dcb.StopBits = NativeMethods.TWOSTOPBITS;
                    break;
                default:
                    Debug.Assert(false, "Invalid value for stopBits");
                    break;
            }

            dcb.Parity = (byte) parity;
            // SetDcbFlag, GetDcbFlag expose access to each of the relevant bits of the 32-bit integer
            // storing all flags of the DCB.  C# provides no direct means of manipulating bit fields, so
            // this is the solution.
            SetDcbFlag(NativeMethods.FPARITY, ((parity == Parity.None)  ?  0  :  1));

            SetDcbFlag(NativeMethods.FBINARY, 1);   // always true for communications resources

            // set DCB fields implied by default and the arguments given.
            // Boolean fields in C# must become 1, 0 to properly set the bit flags in the unmanaged DCB struct

            SetDcbFlag(NativeMethods.FOUTXCTSFLOW, ((handshake == Handshake.RequestToSend ||
                handshake == Handshake.RequestToSendXOnXOff) ? 1 : 0));
            // SetDcbFlag(NativeMethods.FOUTXDSRFLOW, (dsrTimeout != 0L) ? 1 : 0);
            SetDcbFlag(NativeMethods.FOUTXDSRFLOW, 0); // dsrTimeout is always set to 0.
            SetDcbFlag(NativeMethods.FDTRCONTROL, NativeMethods.DTR_CONTROL_DISABLE);
            SetDcbFlag(NativeMethods.FDSRSENSITIVITY, 0); // this should remain off
            SetDcbFlag(NativeMethods.FINX, (handshake == Handshake.XOnXOff || handshake == Handshake.RequestToSendXOnXOff) ? 1 : 0);
            SetDcbFlag(NativeMethods.FOUTX,(handshake == Handshake.XOnXOff || handshake == Handshake.RequestToSendXOnXOff) ? 1 : 0);

            // if no parity, we have no error character (i.e. ErrorChar = '\0' or null character)
            if (parity != Parity.None)
            {
                SetDcbFlag(NativeMethods.FERRORCHAR, (parityReplace != '\0') ? 1 : 0);
                dcb.ErrorChar = parityReplace;
            }
            else
            {
                SetDcbFlag(NativeMethods.FERRORCHAR, 0);
                dcb.ErrorChar = (byte) '\0';
            }

            // this method only runs once in the constructor, so we only have the default value to use.
            // Later the user may change this via the NullDiscard property.
            SetDcbFlag(NativeMethods.FNULL, discardNull ? 1 : 0);


            // Setting RTS control, which is RTS_CONTROL_HANDSHAKE if RTS / RTS-XOnXOff handshaking
            // used, RTS_ENABLE (RTS pin used during operation) if rtsEnable true but XOnXoff / No handshaking
            // used, and disabled otherwise.
            if ((handshake == Handshake.RequestToSend ||
                handshake == Handshake.RequestToSendXOnXOff))
            {
                SetDcbFlag(NativeMethods.FRTSCONTROL, NativeMethods.RTS_CONTROL_HANDSHAKE);
            }
            else if (GetDcbFlag(NativeMethods.FRTSCONTROL) == NativeMethods.RTS_CONTROL_HANDSHAKE)
            {
                SetDcbFlag(NativeMethods.FRTSCONTROL, NativeMethods.RTS_CONTROL_DISABLE);
            }

            dcb.XonChar = NativeMethods.DEFAULTXONCHAR;             // may be exposed later but for now, constant
            dcb.XoffChar = NativeMethods.DEFAULTXOFFCHAR;

            // minimum number of bytes allowed in each buffer before flow control activated
            // heuristically, this has been set at 1/4 of the buffer size
            dcb.XonLim = dcb.XoffLim = (ushort) (commProp.dwCurrentRxQueue / 4);

            dcb.EofChar = NativeMethods.EOFCHAR;

            //OLD MSCOMM: dcb.EvtChar = (byte) 0;
            // now changed to make use of RXFlag WaitCommEvent event => Eof WaitForCommEvent event
            dcb.EvtChar = NativeMethods.EOFCHAR;

            // set DCB structure
            if (UnsafeNativeMethods.SetCommState(_handle, ref dcb) == false)
            {
                InternalResources.WinIOError();
            }
        }

        // Here we provide a method for getting the flags of the Device Control Block structure dcb
        // associated with each instance of SerialStream, i.e. this method gets myStream.dcb.Flags
        // Flags are any of the constants in NativeMethods such as FBINARY, FDTRCONTROL, etc.
        internal int GetDcbFlag(int whichFlag)
        {
            uint mask;

            Debug.Assert(whichFlag >= NativeMethods.FBINARY && whichFlag <= NativeMethods.FDUMMY2, "GetDcbFlag needs to fit into enum!");

            if (whichFlag == NativeMethods.FDTRCONTROL || whichFlag == NativeMethods.FRTSCONTROL)
            {
                mask = 0x3;
            }
            else if (whichFlag == NativeMethods.FDUMMY2)
            {
                mask = 0x1FFFF;
            }
            else
            {
                mask = 0x1;
            }
            uint result = dcb.Flags & (mask << whichFlag);
            return (int) (result >> whichFlag);
        }

        // Since C# applications have to provide a workaround for accessing and setting bitfields in unmanaged code,
        // here we provide methods for getting and setting the Flags field of the Device Control Block structure dcb
        // associated with each instance of SerialStream, i.e. this method sets myStream.dcb.Flags
        // Flags are any of the constants in NativeMethods such as FBINARY, FDTRCONTROL, etc.
        internal void SetDcbFlag(int whichFlag, int setting)
        {
            uint mask;
            setting = setting << whichFlag;

            Debug.Assert(whichFlag >= NativeMethods.FBINARY && whichFlag <= NativeMethods.FDUMMY2, "SetDcbFlag needs to fit into enum!");

            if (whichFlag == NativeMethods.FDTRCONTROL || whichFlag == NativeMethods.FRTSCONTROL)
            {
                mask = 0x3;
            }
            else if (whichFlag == NativeMethods.FDUMMY2)
            {
                mask = 0x1FFFF;
            }
            else
            {
                mask = 0x1;
            }

            // clear the region
            dcb.Flags &= ~(mask << whichFlag);

            // set the region
            dcb.Flags |= ((uint) setting);
        }

        // ----SUBSECTION: internal methods supporting public read/write methods-------*

        [ResourceExposure(ResourceScope.None)]
        [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
        unsafe private SerialStreamAsyncResult BeginReadCore(byte[] array, int offset, int numBytes, AsyncCallback userCallback, Object stateObject)
        {

            // Create and store async stream class library specific data in the
            // async result
            SerialStreamAsyncResult asyncResult = new SerialStreamAsyncResult();
            asyncResult._userCallback = userCallback;
            asyncResult._userStateObject = stateObject;
            asyncResult._isWrite = false;

            // For Synchronous IO, I could go with either a callback and using
            // the managed Monitor class, or I could create a handle and wait on it.
            ManualResetEvent waitHandle = new ManualResetEvent(false);
            asyncResult._waitHandle = waitHandle;

            // Create a managed overlapped class
            // We will set the file offsets later
            Overlapped overlapped = new Overlapped(0, 0, IntPtr.Zero, asyncResult);

            // Pack the Overlapped class, and store it in the async result
            NativeOverlapped* intOverlapped = overlapped.Pack(IOCallback, array);

            asyncResult._overlapped = intOverlapped;

            // queue an async ReadFile operation and pass in a packed overlapped
            //int r = ReadFile(_handle, array, numBytes, null, intOverlapped);
            int hr = 0;
            int r = ReadFileNative(array, offset, numBytes,
             intOverlapped, out hr);

            // ReadFile, the OS version, will return 0 on failure.  But
            // my ReadFileNative wrapper returns -1.  My wrapper will return
            // the following:
            // On error, r==-1.
            // On async requests that are still pending, r==-1 w/ hr==ERROR_IO_PENDING
            // on async requests that completed sequentially, r==0
            // Note that you will NEVER RELIABLY be able to get the number of bytes
            // read back from this call when using overlapped structures!  You must
            // not pass in a non-null lpNumBytesRead to ReadFile when using
            // overlapped structures!
            if (r==-1)
            {
                if (hr != NativeMethods.ERROR_IO_PENDING)
                {
                    if (hr == NativeMethods.ERROR_HANDLE_EOF)
                        InternalResources.EndOfFile();
                    else
                        InternalResources.WinIOError(hr, String.Empty);
                }
            }

            return asyncResult;
        }

        [ResourceExposure(ResourceScope.None)]
        [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
        unsafe private SerialStreamAsyncResult BeginWriteCore(byte[] array, int offset, int numBytes, AsyncCallback userCallback, Object stateObject)
        {
            // Create and store async stream class library specific data in the
            // async result
            SerialStreamAsyncResult asyncResult = new SerialStreamAsyncResult();
            asyncResult._userCallback = userCallback;
            asyncResult._userStateObject = stateObject;
            asyncResult._isWrite = true;

            // For Synchronous IO, I could go with either a callback and using
            // the managed Monitor class, or I could create a handle and wait on it.
            ManualResetEvent waitHandle = new ManualResetEvent(false);
            asyncResult._waitHandle = waitHandle;

            // Create a managed overlapped class
            // We will set the file offsets later
            Overlapped overlapped = new Overlapped(0, 0, IntPtr.Zero, asyncResult);

            // Pack the Overlapped class, and store it in the async result
            NativeOverlapped* intOverlapped = overlapped.Pack(IOCallback, array);

            asyncResult._overlapped = intOverlapped;

            int hr = 0;
            // queue an async WriteFile operation and pass in a packed overlapped
            int r = WriteFileNative(array, offset, numBytes, intOverlapped, out hr);

            // WriteFile, the OS version, will return 0 on failure.  But
            // my WriteFileNative wrapper returns -1.  My wrapper will return
            // the following:
            // On error, r==-1.
            // On async requests that are still pending, r==-1 w/ hr==ERROR_IO_PENDING
            // On async requests that completed sequentially, r==0
            // Note that you will NEVER RELIABLY be able to get the number of bytes
            // written back from this call when using overlapped IO!  You must
            // not pass in a non-null lpNumBytesWritten to WriteFile when using
            // overlapped structures!
            if (r==-1)
            {
                if (hr != NativeMethods.ERROR_IO_PENDING)
                {

                    if (hr == NativeMethods.ERROR_HANDLE_EOF)
                        InternalResources.EndOfFile();
                    else
                        InternalResources.WinIOError(hr, String.Empty);
                }
            }
            return asyncResult;
        }


        // Internal method, wrapping the PInvoke to ReadFile().
        private unsafe int ReadFileNative(byte[] bytes, int offset, int count, NativeOverlapped* overlapped, out int hr)
        {

            // Don't corrupt memory when multiple threads are erroneously writing
            // to this stream simultaneously.
            if (bytes.Length - offset < count)
                throw new IndexOutOfRangeException(SR.GetString(SR.IndexOutOfRange_IORaceCondition));

            // You can't use the fixed statement on an array of length 0.
            if (bytes.Length==0)
            {
                hr = 0;
                return 0;
            }

            int r = 0;
            int numBytesRead = 0;

            fixed(byte* p = bytes)
            {
                if (isAsync)
                    r = UnsafeNativeMethods.ReadFile(_handle, p + offset, count, IntPtr.Zero, overlapped);
                else
                    r = UnsafeNativeMethods.ReadFile(_handle, p + offset, count, out numBytesRead, IntPtr.Zero);
            }

            if (r==0)
            {
                hr = Marshal.GetLastWin32Error();

                // Note: we should never silently ignore an error here without some
                // extra work.  We must make sure that BeginReadCore won't return an
                // IAsyncResult that will cause EndRead to block, since the OS won't
                // call AsyncFSCallback for us.

                // For invalid handles, detect the error and mark our handle
                // as closed to give slightly better error messages.  Also
                // help ensure we avoid handle recycling bugs.
                if (hr == NativeMethods.ERROR_INVALID_HANDLE)
                    _handle.SetHandleAsInvalid();

                return -1;
            }
            else
                hr = 0;
            return numBytesRead;
        }

        private unsafe int WriteFileNative(byte[] bytes, int offset, int count, NativeOverlapped* overlapped, out int hr)
        {

            // Don't corrupt memory when multiple threads are erroneously writing
            // to this stream simultaneously.  (Note that the OS is reading from
            // the array we pass to WriteFile, but if we read beyond the end and
            // that memory isn't allocated, we could get an AV.)
            if (bytes.Length - offset < count)
                throw new IndexOutOfRangeException(SR.GetString(SR.IndexOutOfRange_IORaceCondition));

            // You can't use the fixed statement on an array of length 0.
            if (bytes.Length==0)
            {
                hr = 0;
                return 0;
            }

            int numBytesWritten = 0;
            int r = 0;

            fixed(byte* p = bytes)
            {
                if (isAsync)
                    r = UnsafeNativeMethods.WriteFile(_handle, p + offset, count, IntPtr.Zero, overlapped);
                else
                    r = UnsafeNativeMethods.WriteFile(_handle, p + offset, count, out numBytesWritten, IntPtr.Zero);
            }

            if (r==0)
            {
                hr = Marshal.GetLastWin32Error();
                // Note: we should never silently ignore an error here without some
                // extra work.  We must make sure that BeginWriteCore won't return an
                // IAsyncResult that will cause EndWrite to block, since the OS won't
                // call AsyncFSCallback for us.

                // For invalid handles, detect the error and mark our handle
                // as closed to give slightly better error messages.  Also
                // help ensure we avoid handle recycling bugs.
                if (hr == NativeMethods.ERROR_INVALID_HANDLE)
                    _handle.SetHandleAsInvalid();

                return -1;
            }
            else
                hr = 0;
            return numBytesWritten;
        }

        // ----SUBSECTION: internal methods supporting events/async operation------*

        // This is a the callback prompted when a thread completes any async I/O operation.
        unsafe private static void AsyncFSCallback(uint errorCode, uint numBytes, NativeOverlapped* pOverlapped)
        {
            // Unpack overlapped
            Overlapped overlapped = Overlapped.Unpack(pOverlapped);

            // Extract async the result from overlapped structure
            SerialStreamAsyncResult asyncResult =
                (SerialStreamAsyncResult)overlapped.AsyncResult;
            asyncResult._numBytes = (int)numBytes;

            asyncResult._errorCode = (int)errorCode;

            // Call the user-provided callback.  Note that it can and often should
            // call EndRead or EndWrite.  There's no reason to use an async
            // delegate here - we're already on a threadpool thread.
            // Note the IAsyncResult's completedSynchronously property must return
            // false here, saying the user callback was called on another thread.
            asyncResult._completedSynchronously = false;
            asyncResult._isComplete = true;

            // The OS does not signal this event.  We must do it ourselves.
            // But don't close it if the user callback called EndXxx, 
            // which then closed the manual reset event already.
            ManualResetEvent wh = asyncResult._waitHandle;
            if (wh != null) {
                bool r = wh.Set();
                if (!r) InternalResources.WinIOError();
            }

            AsyncCallback userCallback = asyncResult._userCallback;
            if (userCallback != null)
                userCallback(asyncResult);
        }


        // ----SECTION: internal classes --------*

        internal sealed class EventLoopRunner {
            private WeakReference streamWeakReference;
            internal ManualResetEvent eventLoopEndedSignal = new ManualResetEvent(false);
            internal ManualResetEvent waitCommEventWaitHandle = new ManualResetEvent(false);
            private SafeFileHandle handle = null;
            private bool isAsync;
            internal bool endEventLoop;
            private int eventsOccurred;

            WaitCallback callErrorEvents;
            WaitCallback callReceiveEvents;
            WaitCallback callPinEvents;
            IOCompletionCallback freeNativeOverlappedCallback;

#if DEBUG 
            private readonly string portName;
#endif            
            internal unsafe EventLoopRunner(SerialStream stream) {
                handle = stream._handle;
                streamWeakReference = new WeakReference(stream);

                callErrorEvents = new WaitCallback(CallErrorEvents);
                callReceiveEvents = new WaitCallback(CallReceiveEvents );
                callPinEvents = new WaitCallback(CallPinEvents);
                freeNativeOverlappedCallback = new IOCompletionCallback(FreeNativeOverlappedCallback);
                isAsync = stream.isAsync;
#if DEBUG 
                portName = stream.portName;
#endif            
            }

            internal bool ShutdownLoop {
                get {
                    return endEventLoop;
                }
            }
            
            // This is the blocking method that waits for an event to occur.  It wraps the SDK's WaitCommEvent function.
            [ResourceExposure(ResourceScope.None)]
            [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
            [SuppressMessage("Microsoft.Interoperability", "CA1404:CallGetLastErrorImmediatelyAfterPInvoke", Justification = "this is debug-only code")]
            internal unsafe void WaitForCommEvent()
            {
                int unused = 0;
                bool doCleanup = false;
                NativeOverlapped* intOverlapped = null;
                while (!ShutdownLoop) {
                    SerialStreamAsyncResult asyncResult = null;
                    if (isAsync) {
                        asyncResult = new SerialStreamAsyncResult();
                        asyncResult._userCallback = null;
                        asyncResult._userStateObject = null;
                        asyncResult._isWrite = false;

                        // we're going to use _numBytes for something different in this loop.  In this case, both 
                        // freeNativeOverlappedCallback and this thread will decrement that value.  Whichever one decrements it
                        // to zero will be the one to free the native overlapped.  This guarantees the overlapped gets freed
                        // after both the callback and GetOverlappedResult have had a chance to use it. 
                        asyncResult._numBytes = 2;
                        asyncResult._waitHandle = waitCommEventWaitHandle;

                        waitCommEventWaitHandle.Reset();
                        Overlapped overlapped = new Overlapped(0, 0, waitCommEventWaitHandle.SafeWaitHandle.DangerousGetHandle(), asyncResult);
                        // Pack the Overlapped class, and store it in the async result
                        intOverlapped = overlapped.Pack(freeNativeOverlappedCallback, null);
                    }

                    fixed (int* eventsOccurredPtr = &eventsOccurred) {

                        if (UnsafeNativeMethods.WaitCommEvent(handle, eventsOccurredPtr, intOverlapped) == false)
                        {
                            int hr = Marshal.GetLastWin32Error();
                            // When a device is disconnected unexpectedly from a serial port, there appear to be
                            // at least two error codes Windows or drivers may return.
                            if (hr == NativeMethods.ERROR_ACCESS_DENIED || hr == NativeMethods.ERROR_BAD_COMMAND) {
                                doCleanup = true;
                                break;
                            }
                            if (hr == NativeMethods.ERROR_IO_PENDING)
                            {
                                Debug.Assert(isAsync, "The port is not open for async, so we should not get ERROR_IO_PENDING from WaitCommEvent");
                                int error;
                                    
                                // if we get IO pending, MSDN says we should wait on the WaitHandle, then call GetOverlappedResult
                                // to get the results of WaitCommEvent. 
                                bool success = waitCommEventWaitHandle.WaitOne();
                                Debug.Assert(success, "waitCommEventWaitHandle.WaitOne() returned error " + Marshal.GetLastWin32Error());

                                do { 
                                    // NOTE: GetOverlappedResult will modify the original pointer passed into WaitCommEvent.
                                    success = UnsafeNativeMethods.GetOverlappedResult(handle, intOverlapped, ref unused, false);
                                    error = Marshal.GetLastWin32Error();
                                }
                                while (error == NativeMethods.ERROR_IO_INCOMPLETE && !ShutdownLoop && !success);

                                if (!success) {
                                    // Ignore ERROR_IO_INCOMPLETE and ERROR_INVALID_PARAMETER, because there's a chance we'll get
                                    // one of those while shutting down 
                                    if (! ( (error == NativeMethods.ERROR_IO_INCOMPLETE || error == NativeMethods.ERROR_INVALID_PARAMETER) && ShutdownLoop))
                                        Debug.Assert(false, "GetOverlappedResult returned error, we might leak intOverlapped memory" + error.ToString(CultureInfo.InvariantCulture));
                                }
                            }
                            else if (hr != NativeMethods.ERROR_INVALID_PARAMETER) {
                                // ignore ERROR_INVALID_PARAMETER errors.  WaitCommError seems to return this
                                // when SetCommMask is changed while it's blocking (like we do in Dispose())
                                Debug.Assert(false, "WaitCommEvent returned error " + hr);
                            }
                        }
                    }

                    if (!ShutdownLoop)
                        CallEvents(eventsOccurred);

                    if (isAsync) {   
                        if (Interlocked.Decrement(ref asyncResult._numBytes) == 0)
                            Overlapped.Free(intOverlapped);
                    }
                } // while (!ShutdownLoop)

                if (doCleanup) {
                    // the rest will be handled in Dispose()
                    endEventLoop = true;
                    Overlapped.Free(intOverlapped);
                }
                eventLoopEndedSignal.Set();
            }

            private unsafe void FreeNativeOverlappedCallback(uint errorCode, uint numBytes, NativeOverlapped* pOverlapped) {
                // Unpack overlapped
                Overlapped overlapped = Overlapped.Unpack(pOverlapped);
                
                // Extract the async result from overlapped structure
                SerialStreamAsyncResult asyncResult =
                    (SerialStreamAsyncResult)overlapped.AsyncResult;

                if (Interlocked.Decrement(ref asyncResult._numBytes) == 0)
                    Overlapped.Free(pOverlapped);
            }
            
            private void CallEvents(int nativeEvents)
            {
                // EV_ERR includes only CE_FRAME, CE_OVERRUN, and CE_RXPARITY
                // To catch errors such as CE_RXOVER, we need to call CleanCommErrors bit more regularly. 
                // EV_RXCHAR is perhaps too loose an event to look for overflow errors but a safe side to err...
                if ((nativeEvents & (NativeMethods.EV_ERR | NativeMethods.EV_RXCHAR)) != 0) {
                    int errors = 0;
                    if (UnsafeNativeMethods.ClearCommError(handle, ref errors, IntPtr.Zero) == false) {

                        //InternalResources.WinIOError();

                        // We don't want to throw an exception from the background thread which is un-catchable and hence tear down the process.
                        // At present we don't have a first class event that we can raise for this class of fatal errors. One possibility is 
                        // to overload SeralErrors event to include another enum (perhaps CE_IOE) that we can use for this purpose. 
                        // In the absene of that, it is better to eat this error silently than tearing down the process (lesser of the evil). 
                        // This uncleared comm error will most likely ---- up when the device is accessed by other APIs (such as Read) on the 
                        // main thread and hence become known. It is bit roundabout but acceptable.  
                        //  
                        // Shutdown the event runner loop (probably bit drastic but we did come across a fatal error). 
                        // Defer actual dispose chores until finalization though. 
                        endEventLoop = true;
                        Thread.MemoryBarrier();
                        return;
                    }

                    errors = errors & errorEvents;
                    // 



                    if (errors != 0) {
                        ThreadPool.QueueUserWorkItem(callErrorEvents, errors);
                    }
                }
            
                // now look for pin changed and received events.
                if ((nativeEvents & pinChangedEvents) != 0) {
                    ThreadPool.QueueUserWorkItem(callPinEvents, nativeEvents);
                }
            
                if ((nativeEvents & receivedEvents) != 0) {
                    ThreadPool.QueueUserWorkItem(callReceiveEvents, nativeEvents);
                }
            }

            
            private void CallErrorEvents(object state) {
                int errors = (int) state;
                SerialStream stream = (SerialStream) streamWeakReference.Target;
                if (stream == null)
                    return;
                
                if (stream.ErrorReceived != null) {
                    if ((errors & (int) SerialError.TXFull) != 0)
                        stream.ErrorReceived(stream, new SerialErrorReceivedEventArgs(SerialError.TXFull));
                
                    if ((errors & (int) SerialError.RXOver) != 0)
                        stream.ErrorReceived(stream, new SerialErrorReceivedEventArgs(SerialError.RXOver));
                
                    if ((errors & (int) SerialError.Overrun) != 0)
                        stream.ErrorReceived(stream, new SerialErrorReceivedEventArgs(SerialError.Overrun));
                
                    if ((errors & (int) SerialError.RXParity) != 0)
                        stream.ErrorReceived(stream, new SerialErrorReceivedEventArgs(SerialError.RXParity));
                
                    if ((errors & (int) SerialError.Frame) != 0)
                        stream.ErrorReceived(stream, new SerialErrorReceivedEventArgs(SerialError.Frame));
                }
                
                stream = null;
            }

            private void CallReceiveEvents(object state) {
                int nativeEvents = (int) state;
                SerialStream stream = (SerialStream) streamWeakReference.Target;
                if (stream == null)
                    return;
                
                if (stream.DataReceived != null) {
                    if ((nativeEvents & (int) SerialData.Chars) != 0)
                        stream.DataReceived(stream, new SerialDataReceivedEventArgs(SerialData.Chars));
                    if ((nativeEvents & (int) SerialData.Eof) != 0)
                        stream.DataReceived(stream, new SerialDataReceivedEventArgs(SerialData.Eof));
                }
                
                stream = null;
            }

            private void CallPinEvents(object state) {
                int nativeEvents = (int) state;
                
                SerialStream stream = (SerialStream) streamWeakReference.Target;
                if (stream == null)
                    return;
                
                if (stream.PinChanged != null) {
                    if ((nativeEvents & (int) SerialPinChange.CtsChanged) != 0)
                        stream.PinChanged(stream, new SerialPinChangedEventArgs(SerialPinChange.CtsChanged));
                
                    if ((nativeEvents & (int) SerialPinChange.DsrChanged) != 0)
                        stream.PinChanged(stream, new SerialPinChangedEventArgs(SerialPinChange.DsrChanged));
                
                    if ((nativeEvents & (int) SerialPinChange.CDChanged) != 0)
                        stream.PinChanged(stream, new SerialPinChangedEventArgs(SerialPinChange.CDChanged));
                
                    if ((nativeEvents & (int) SerialPinChange.Ring) != 0)
                        stream.PinChanged(stream, new SerialPinChangedEventArgs(SerialPinChange.Ring));
                
                    if ((nativeEvents & (int) SerialPinChange.Break) != 0)
                        stream.PinChanged(stream, new SerialPinChangedEventArgs(SerialPinChange.Break));
                }
                
                stream = null;
            }
            
        }

        
        // This is an internal object implementing IAsyncResult with fields
        // for all of the relevant data necessary to complete the IO operation.
        // This is used by AsyncFSCallback and all async methods.
        unsafe internal sealed class SerialStreamAsyncResult : IAsyncResult
        {
            // User code callback
            internal AsyncCallback _userCallback;

            internal Object _userStateObject;

            internal bool _isWrite;     // Whether this is a read or a write
            internal bool _isComplete;
            internal bool _completedSynchronously;  // Which thread called callback

            internal ManualResetEvent _waitHandle;
            internal int _EndXxxCalled;   // Whether we've called EndXxx already.
            internal int _numBytes;     // number of bytes read OR written
            internal int _errorCode;
            internal NativeOverlapped* _overlapped;

            public Object AsyncState
            {
                get { return _userStateObject; }
            }

            public bool IsCompleted
            {
                get { return _isComplete; }
            }

            public WaitHandle AsyncWaitHandle
            {
                get { 
                    /*
                      // Consider uncommenting this someday soon - the EventHandle 
                      // in the Overlapped struct is really useless half of the 
                      // time today since the OS doesn't signal it.  If users call
                      // EndXxx after the OS call happened to complete, there's no
                      // reason to create a synchronization primitive here.  Fixing
                      // this will save us some perf, assuming we can correctly
                      // initialize the ManualResetEvent. 
                    if (_waitHandle == null) {
                        ManualResetEvent mre = new ManualResetEvent(false);
                        if (_overlapped != null && _overlapped->EventHandle != IntPtr.Zero)
                            mre.Handle = _overlapped->EventHandle;
                        if (_isComplete)
                            mre.Set();
                        _waitHandle = mre;
                    }
                    */
                    return _waitHandle;
                }
            }

            // Returns true iff the user callback was called by the thread that
            // called BeginRead or BeginWrite.  If we use an async delegate or
            // threadpool thread internally, this will be false.  This is used
            // by code to determine whether a successive call to BeginRead needs
            // to be done on their main thread or in their callback to avoid a
            // stack overflow on many reads or writes.
            public bool CompletedSynchronously
            {
                get { return _completedSynchronously; }
            }
        }
    }
}

实际的应用调用是在这个地方:

private void CallEvents(int nativeEvents)
            {
                // EV_ERR includes only CE_FRAME, CE_OVERRUN, and CE_RXPARITY
                // To catch errors such as CE_RXOVER, we need to call CleanCommErrors bit more regularly. 
                // EV_RXCHAR is perhaps too loose an event to look for overflow errors but a safe side to err...
                if ((nativeEvents & (NativeMethods.EV_ERR | NativeMethods.EV_RXCHAR)) != 0) {
                    int errors = 0;
                    if (UnsafeNativeMethods.ClearCommError(handle, ref errors, IntPtr.Zero) == false) {

                        //InternalResources.WinIOError();

                        // We don't want to throw an exception from the background thread which is un-catchable and hence tear down the process.
                        // At present we don't have a first class event that we can raise for this class of fatal errors. One possibility is 
                        // to overload SeralErrors event to include another enum (perhaps CE_IOE) that we can use for this purpose. 
                        // In the absene of that, it is better to eat this error silently than tearing down the process (lesser of the evil). 
                        // This uncleared comm error will most likely ---- up when the device is accessed by other APIs (such as Read) on the 
                        // main thread and hence become known. It is bit roundabout but acceptable.  
                        //  
                        // Shutdown the event runner loop (probably bit drastic but we did come across a fatal error). 
                        // Defer actual dispose chores until finalization though. 
                        endEventLoop = true;
                        Thread.MemoryBarrier();
                        return;
                    }

                    errors = errors & errorEvents;
                    // 



                    if (errors != 0) {
                        ThreadPool.QueueUserWorkItem(callErrorEvents, errors);
                    }
                }
            
                // now look for pin changed and received events.
                if ((nativeEvents & pinChangedEvents) != 0) {
                    ThreadPool.QueueUserWorkItem(callPinEvents, nativeEvents);
                }
            
                if ((nativeEvents & receivedEvents) != 0) {
                    ThreadPool.QueueUserWorkItem(callReceiveEvents, nativeEvents);
                }
            }

可以看到ThreadPool,在整个框架应用中,贯穿了大量的异步调用,在所有的通知回业务调用时都是通过线程池机制,这也是基于业务线程的处理周期时间不可控所决定的,因为不可能让所有的消息都缓冲在缓冲队列,而不执行。

对IO或设备的通信处理机制,Windows下效率最高的是IO完成端口,这里也是利用IO完成端口实现的,不过这里的这段代码确实不大好理解,但是下面这段代码我认为写出了水平,把信号量,可重叠复用,IOCP全部结合起来了。所以这块的效率其实是很高的,但驱动这块的执行,毕竟还是属于一个独立的线程在一直轮询处理,所以就只能向前而没法面向承后了。分析我们可以发现,在多线程和实时控制中,牵扯到的线程调度可能不仅仅是我们的业务线程,还有这么多的底层模块的线程。


   internal unsafe EventLoopRunner(SerialStream stream) {
                handle = stream._handle;
                streamWeakReference = new WeakReference(stream);

                callErrorEvents = new WaitCallback(CallErrorEvents);
                callReceiveEvents = new WaitCallback(CallReceiveEvents );
                callPinEvents = new WaitCallback(CallPinEvents);
                freeNativeOverlappedCallback = new IOCompletionCallback(FreeNativeOverlappedCallback);
                isAsync = stream.isAsync;
#if DEBUG 
                portName = stream.portName;
#endif            
            }

            internal bool ShutdownLoop {
                get {
                    return endEventLoop;
                }
            }
            
            // This is the blocking method that waits for an event to occur.  It wraps the SDK's WaitCommEvent function.
            [ResourceExposure(ResourceScope.None)]
            [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
            [SuppressMessage("Microsoft.Interoperability", "CA1404:CallGetLastErrorImmediatelyAfterPInvoke", Justification = "this is debug-only code")]
            internal unsafe void WaitForCommEvent()
            {
                int unused = 0;
                bool doCleanup = false;
                NativeOverlapped* intOverlapped = null;
                while (!ShutdownLoop) {
                    SerialStreamAsyncResult asyncResult = null;
                    if (isAsync) {
                        asyncResult = new SerialStreamAsyncResult();
                        asyncResult._userCallback = null;
                        asyncResult._userStateObject = null;
                        asyncResult._isWrite = false;

                        // we're going to use _numBytes for something different in this loop.  In this case, both 
                        // freeNativeOverlappedCallback and this thread will decrement that value.  Whichever one decrements it
                        // to zero will be the one to free the native overlapped.  This guarantees the overlapped gets freed
                        // after both the callback and GetOverlappedResult have had a chance to use it. 
                        asyncResult._numBytes = 2;
                        asyncResult._waitHandle = waitCommEventWaitHandle;

                        waitCommEventWaitHandle.Reset();
                        Overlapped overlapped = new Overlapped(0, 0, waitCommEventWaitHandle.SafeWaitHandle.DangerousGetHandle(), asyncResult);
                        // Pack the Overlapped class, and store it in the async result
                        intOverlapped = overlapped.Pack(freeNativeOverlappedCallback, null);
                    }

                    fixed (int* eventsOccurredPtr = &eventsOccurred) {

                        if (UnsafeNativeMethods.WaitCommEvent(handle, eventsOccurredPtr, intOverlapped) == false)
                        {
                            int hr = Marshal.GetLastWin32Error();
                            // When a device is disconnected unexpectedly from a serial port, there appear to be
                            // at least two error codes Windows or drivers may return.
                            if (hr == NativeMethods.ERROR_ACCESS_DENIED || hr == NativeMethods.ERROR_BAD_COMMAND) {
                                doCleanup = true;
                                break;
                            }
                            if (hr == NativeMethods.ERROR_IO_PENDING)
                            {
                                Debug.Assert(isAsync, "The port is not open for async, so we should not get ERROR_IO_PENDING from WaitCommEvent");
                                int error;
                                    
                                // if we get IO pending, MSDN says we should wait on the WaitHandle, then call GetOverlappedResult
                                // to get the results of WaitCommEvent. 
                                bool success = waitCommEventWaitHandle.WaitOne();
                                Debug.Assert(success, "waitCommEventWaitHandle.WaitOne() returned error " + Marshal.GetLastWin32Error());

                                do { 
                                    // NOTE: GetOverlappedResult will modify the original pointer passed into WaitCommEvent.
                                    success = UnsafeNativeMethods.GetOverlappedResult(handle, intOverlapped, ref unused, false);
                                    error = Marshal.GetLastWin32Error();
                                }
                                while (error == NativeMethods.ERROR_IO_INCOMPLETE && !ShutdownLoop && !success);

                                if (!success) {
                                    // Ignore ERROR_IO_INCOMPLETE and ERROR_INVALID_PARAMETER, because there's a chance we'll get
                                    // one of those while shutting down 
                                    if (! ( (error == NativeMethods.ERROR_IO_INCOMPLETE || error == NativeMethods.ERROR_INVALID_PARAMETER) && ShutdownLoop))
                                        Debug.Assert(false, "GetOverlappedResult returned error, we might leak intOverlapped memory" + error.ToString(CultureInfo.InvariantCulture));
                                }
                            }
                            else if (hr != NativeMethods.ERROR_INVALID_PARAMETER) {
                                // ignore ERROR_INVALID_PARAMETER errors.  WaitCommError seems to return this
                                // when SetCommMask is changed while it's blocking (like we do in Dispose())
                                Debug.Assert(false, "WaitCommEvent returned error " + hr);
                            }
                        }
                    }

                    if (!ShutdownLoop)
                        CallEvents(eventsOccurred);

                    if (isAsync) {   
                        if (Interlocked.Decrement(ref asyncResult._numBytes) == 0)
                            Overlapped.Free(intOverlapped);
                    }
                } // while (!ShutdownLoop)

                if (doCleanup) {
                    // the rest will be handled in Dispose()
                    endEventLoop = true;
                    Overlapped.Free(intOverlapped);
                }
                eventLoopEndedSignal.Set();
            }

            private unsafe void FreeNativeOverlappedCallback(uint errorCode, uint numBytes, NativeOverlapped* pOverlapped) {
                // Unpack overlapped
                Overlapped overlapped = Overlapped.Unpack(pOverlapped);
                
                // Extract the async result from overlapped structure
                SerialStreamAsyncResult asyncResult =
                    (SerialStreamAsyncResult)overlapped.AsyncResult;

                if (Interlocked.Decrement(ref asyncResult._numBytes) == 0)
                    Overlapped.Free(pOverlapped);
            }

通过对串口的整个线程和技术的分析,使我们意识到了,所有的通信都是基于Win32API 的封装,直接调用的操作系统底层已经完整的协议栈,这在任何的语言中都是这样,

我们关心的数据调用时,已经是经过了两级线程的调度包装后的数据,因此为了提高实时系统响应性的需求,和一些特殊场合的处理需求,那就可以剥离其中的一层了,像微软的源代码封装一样,我只需要最后这一层的东西就可以了,这样在一些处理场合,就不需要担心系统的争用引起的负担问题(这只是在特殊应用场合,微软的整个机制是对的,并且是严谨的,只是特殊的场合的响应性是不够的)。


你可能感兴趣的:(.net中的串口类的实时响应性分析)