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