void SendNxtMessagesThread(ThreadContext ctx)
            {
            System.Threading.Thread.CurrentThread.Name = "Connection.SendNxtMessagesThread";

            WaitHandle[] waitHandles = new WaitHandle[2];
            waitHandles[0] = this.MessageToSendSemaphore;
            waitHandles[1] = ctx.StopEvent;
            //
            // We throttle the sends a little bit in order to avoid overwhelming a samantha.
            // Without this throttle, QueryAvailableNXTData has been observed to overwhelm the 
            // samantha at times and to drop messages and / or replies (not sure which).
            //
            // However (heuristic): we only throttle sends when we've got a bit of queue backed up
            //
            int msThrottle = (this.msgReplyTargets.Count > 3) ? 3 : 0;
            int msPrev = 0;
            //
            while (!ctx.StopRequest)
                {
                int iWait = WaitHandle.WaitAny(waitHandles);
                //
                switch (iWait)
                    {
                case 0: // MessageToSendSemaphore
                    {
                    NxtMessage msg = null;
                    lock (this.nxtMessagesToSend)
                        {
                        msg = this.nxtMessagesToSend[0];
                        this.nxtMessagesToSend.RemoveAt(0);
                        }
                    //
                    int msNow = System.Environment.TickCount;
                    int dms   = msPrev + msThrottle - msNow;
                    if (dms > 0)
                        {
                        System.Threading.Thread.Sleep(dms);
                        msNow = System.Environment.TickCount;
                        }
                    //
                    this.Send(msg);
                    msPrev = msNow;
                    break;
                    }
                case 1: // StopEvent
                    break;
                // end switch
                    }
                }
            }
        void PostTelemetryRecordsThread(ThreadContext ctx)
        // Forward incoming telemetry records on to Microsoft Excel
            {
            System.Threading.Thread.CurrentThread.Name = "Connection.PostTelemetryRecordsThread";

            bool fDone = false;
            //
            WaitHandle[] waitHandles = new WaitHandle[2];
            waitHandles[0] = this.TelemetryRecordAvailableEvent;
            waitHandles[1] = ctx.StopEvent;
            //
            for (; !ctx.StopRequest && !fDone ;)
                {
                int iWait = WaitHandle.WaitAny(waitHandles);

                switch (iWait)
                    {
                case 0: // TelemetryRecordAvailableEvent
                    {
                    // There's one or more records there to read. 
                    // Read all that we can.
                    for (;!fDone;)
                        {
                        TelemetryRecord telemetryRecord = null;
                        lock (this.TelemetryRecords)
                            {
                            if (0 == this.TelemetryRecords.Count)
                                break;
                            telemetryRecord = this.TelemetryRecords[0];
                            this.TelemetryRecords.RemoveAt(0);
                            }
                        telemetryRecord.Parse();
                        //
                        if (telemetryRecord.fEndOfRecordSet)
                            {
                            // If DATUM_TYPE.EOF has been transmitted then subsequent 
                            // telemetry records received will go to a new spreadsheet
                            //
                            Program.TheForm.DisconnectTelemetryDestination();
                            }
                        else if (telemetryRecord.data.Count == 0)
                            {
                            // Empty records don't do anything
                            }
                        else
                            {
                            // Non-emptry records are posted to the appropriate worksheet
                            //
                            TelemetryFTCUI.ShowWaitCursorWhile(() =>
                                { 
                                Program.TheForm.OpenTelemetryDestinationIfNecessary(); 
                                });
                            telemetryRecord.PostToSheet();
                            }
                        }
                    break;
                    }
                case 1: // StopEvent
                    break;
                // end switch
                    }
                }
            }
        public virtual void Close()
            {
            if (null != this.sendNxtMessagesThread) this.sendNxtMessagesThread.Stop();
            if (null != this.postTelemetryMessagesThread) this.postTelemetryMessagesThread.Stop();

            this.sendNxtMessagesThread = null;
            this.postTelemetryMessagesThread = null;
            }
 public virtual bool Open(bool fTraceFailure=true)
     {
     if (null == this.sendNxtMessagesThread)
         {
         this.sendNxtMessagesThread = new ThreadContext((ctx) => SendNxtMessagesThread((ThreadContext)ctx));
         this.sendNxtMessagesThread.Start();
         }
     //
     if (null == this.postTelemetryMessagesThread)
         {
         this.postTelemetryMessagesThread = new ThreadContext((ctx) => PostTelemetryRecordsThread((ThreadContext)ctx));
         this.postTelemetryMessagesThread.Start();
         }
     //
     return true;
     }
        //--------------------------------------------------------------------------
        // Data reception
        //--------------------------------------------------------------------------

        unsafe void ReadThread(ThreadContext ctx)
            {
            System.Threading.Thread.CurrentThread.Name = "BluetoothConnection.ReadThread";

            EventWaitHandle     asyncReadCompleteEvent = new EventWaitHandle(false, System.Threading.EventResetMode.ManualReset);
            NativeOverlapped*   pNativeOverlapped      = null;
            byte[]              rgbBuffer              = new byte[64];

            try {
                WaitHandle[] waitHandles = new WaitHandle[2];
                waitHandles[0] = asyncReadCompleteEvent;
                waitHandles[1] = ctx.StopEvent;
                //
                while (!ctx.StopRequest)
                    {
                    // Issue an async read
                    // 
                    asyncReadCompleteEvent.Reset();
                    Overlapped overlapped = new Overlapped(0, 0, asyncReadCompleteEvent.SafeWaitHandle.DangerousGetHandle(), null);
                    pNativeOverlapped = overlapped.Pack(null, rgbBuffer);
                    int cbRead = 0;

                    bool fSuccess = ReadFile(this.hSerialPort, rgbBuffer, rgbBuffer.Length, out cbRead, new IntPtr(pNativeOverlapped));
                    readThreadRunning.Set();
                    if (!fSuccess)
                        {
                        int err = Marshal.GetLastWin32Error();
                        if (err != ERROR_IO_PENDING)
                            ThrowWin32Error(err);
                        }

                    // Wait until either the async read completes or we're asked to stop
                    int iWait = WaitHandle.WaitAny(waitHandles);
                
                    // Process according to which event fired
                    switch (iWait)
                        {
                    case 0: // Async read completed
                        {
                        ThrowIfFail(GetOverlappedResult(this.hSerialPort, new IntPtr(pNativeOverlapped), ref cbRead, System.Convert.ToByte(true)));
                        // Program.Trace("async read complete: 0x{0:08X} 0x{1:08X} cb={2}", new IntPtr(pNativeOverlapped), this.hSerialPort, cbRead);

                        // Record the new data and process any packets that are now complete
                        this.RecordIncomingData(rgbBuffer, cbRead);
                        ProcessPacketIfPossible();

                        System.Threading.Overlapped.Free(pNativeOverlapped);
                        pNativeOverlapped = null;
                        }
                        break;
                    case 1: // StopEvent 
                        break;
                    // end switch
                        }

                    }
                }
            finally 
                {
                CancelIo(this.hSerialPort);
                asyncReadCompleteEvent.Close();

                if (pNativeOverlapped != null)
                    {
                    System.Threading.Overlapped.Free(pNativeOverlapped);
                    pNativeOverlapped = null;
                    }
                }
            }
        //--------------------------------------------------------------------------
        // Data Reception
        //--------------------------------------------------------------------------

        // http://www.beefycode.com/post/Using-Overlapped-IO-from-Managed-Code.aspx

        #pragma warning disable 0618 // warning CS0618: 'System.Threading.WaitHandle.Handle' is obsolete: 'Use the SafeWaitHandle property instead.'

        unsafe void ReadThread(ThreadContext ctx)
            {
            System.Threading.Thread.CurrentThread.Name = "USBConnection.ReadThread";

            EventWaitHandle     asyncReadCompleteEvent = new EventWaitHandle(false, System.Threading.EventResetMode.ManualReset);
            NativeOverlapped*   pNativeOverlapped      = null;
            byte[]              rgbBuffer              = new byte[64];

            try {
                WaitHandle[] waitHandles = new WaitHandle[2];
                waitHandles[0] = asyncReadCompleteEvent;
                waitHandles[1] = ctx.StopEvent;
                //
                // If we get unexpected errors, we stop reading; likely these are caused by a device
                // in the process of disconnecting.
                //
                bool fStop = false;
                //
                while (!fStop && !ctx.StopRequest)
                    {
                    // Issue an async read
                    // 
                    asyncReadCompleteEvent.Reset();
                    Overlapped overlapped = new Overlapped(0, 0, asyncReadCompleteEvent.Handle, null);
                    pNativeOverlapped = overlapped.Pack(null, rgbBuffer);
                    int cbRead = 0;

                    // Program.Trace("issuing async read: 0x{0:08X} 0x{1:08X}", new IntPtr(pNativeOverlappedWrite), this.hWinUSB);
                    bool fSuccess = WinUsb_ReadPipe(
                        this.hWinUSB,
                        bulkInPipe,
                        rgbBuffer,
                        rgbBuffer.Length,
                        out cbRead,
                        new IntPtr(pNativeOverlapped));

                    this.readThreadRunning.Set();

                    if (!fSuccess)
                        {
                        int err = Marshal.GetLastWin32Error();
                        if (err != ERROR_IO_PENDING)
                            {
                            Program.Trace("USB Read: WinUsb_ReadPipe=={0}", err);
                            fStop = true;
                            continue;
                            }
                        }

                    // Wait until either the async read completes or we're asked to stop
                    int iWait = WaitHandle.WaitAny(waitHandles);
                
                    // Process according to which event fired
                    switch (iWait)
                        {
                    case 0: // Async read completed
                        {
                        // Program.Trace("async read complete: 0x{0:08X} 0x{1:08X}", new IntPtr(pNativeOverlappedWrite), this.hWinUSB);
                        if (WinUsb_GetOverlappedResult(this.hWinUSB, new IntPtr(pNativeOverlapped), ref cbRead, System.Convert.ToByte(true)))
                            {
                            ProcessIncomingPacket(rgbBuffer, cbRead);
                            }
                        else
                            {
                            int err = Marshal.GetLastWin32Error();
                            Program.Trace("USB Read: WinUsb_GetOverlappedResult=={0}", err);
                            fStop = true;
                            }
                        //
                        System.Threading.Overlapped.Free(pNativeOverlapped);
                        pNativeOverlapped = null;
                        }
                        break;
                    case 1: // StopEvent 
                        // Program.Trace("async read stop requested");
                        break;
                    // end switch
                        }
                    }
                }
            finally 
                {
                // Program.Trace("async cleanup: 0x{0:08X} 0x{1:08X}", new IntPtr(pNativeOverlappedWrite), this.hWinUSB);
                WinUsb_AbortPipe(this.hWinUSB, bulkInPipe);
                asyncReadCompleteEvent.Close();

                if (pNativeOverlapped != null)
                    {
                    System.Threading.Overlapped.Free(pNativeOverlapped);
                    pNativeOverlapped = null;
                    }
                }
            }
        void ReadThread(ThreadContext ctx)
            {
            System.Threading.Thread.CurrentThread.Name = "IPConnection.ReadThread";

            EventWaitHandle      asyncReadCompleteEvent = new EventWaitHandle(false, System.Threading.EventResetMode.ManualReset);
            SocketAsyncEventArgs socketAsyncEventArgs   = new SignallingSocketEventArgs(asyncReadCompleteEvent);
            byte[]               rgbBuffer              = new byte[64];

            socketAsyncEventArgs.SetBuffer(rgbBuffer, 0, rgbBuffer.Length);

            try {
                WaitHandle[] waitHandles = new WaitHandle[2];
                waitHandles[0] = asyncReadCompleteEvent;
                waitHandles[1] = ctx.StopEvent;

                bool fStop = false;

                while (!fStop && !ctx.StopRequest)
                    {
                    // Issue an async read
                    //
                    asyncReadCompleteEvent.Reset();
                    bool fAsync = true;
                    this.SocketMonitorLock(() =>
                        {
                        fAsync = this.socket.ReceiveAsync(socketAsyncEventArgs);
                        });
                    if (!fAsync)
                        {
                        // IO operation completed synchronously
                        asyncReadCompleteEvent.Set();
                        }

                    this.readThreadRunning.Set();

                    // Wait until either the async read completes or we're asked to stop
                    int iWait = WaitHandle.WaitAny(waitHandles);
                
                    // Process according to which event fired
                    switch (iWait)
                        {
                    case 0: // Async read completed
                        {
                        if (socketAsyncEventArgs.BytesTransferred > 0)
                            {
                            if (socketAsyncEventArgs.SocketError == SocketError.Success)
                                {
                                // Program.Trace("IP Read: incoming packet");
                                ProcessIncomingPacket(rgbBuffer, socketAsyncEventArgs.BytesTransferred);
                                }
                            else
                                {
                                Program.Trace("IP Read: unexpected async result: cb={0} err={1}", socketAsyncEventArgs.BytesTransferred, socketAsyncEventArgs.SocketError);
                                fStop = true;
                                }
                            }
                        else
                            {
                            // Program.Trace("IP Read: read completed with zero bytes");
                            }
                        }
                        break;
                    case 1: // StopEvent 
                        // Program.Trace("async read stop requested");
                        break;
                    // end switch
                        }
                    }
                }
            finally
                {
                }
            }