2
\$\begingroup\$

I'm using the SerialPort in C# using Visual Studio 2019.

My SerialPort has 2 "pump" tasks, which are private members of my SerialPort, and are started during an Initialization method:

private Task _RxPump; // Task to receive commands in the background
private Task _TxPump; // Task to transmit commands in the background

In both pumps, I use a loop as below:

while (_serialPort.IsOpen)
{
  // Do the Tx or Rx work
}

When I close the SerialPort, both tasks will run to completion (RanToCompletion), because they exit the while loop if I close the SerialPort. I want to exit my close method only if both tasks are really terminated. I'm using a Task.WaitAll for that.

public bool Close()
{
    if (!_serialPort.IsOpen)
        return false;

    try
    {
        _serialPort.Write("RESET\n");
        _serialPort.Close();

        Task[] tasks = { _TxPump, _RxPump };
        Task.WaitAll(tasks, 100);

        return true;
    }
    catch (Exception)
    {
        return false;
    }
}

I just wonder if there is no more elegant syntax possible to wait for the 2 tasks using a timeout. I know that "Task.WaitAll(_TxPump, _RxPump, 100)" doesn't work.

The full code of my SerialPort class is below:

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO.Ports;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace SerialPortTest
{
    class COMDevice
    {
        private readonly byte[] LF = { (byte)'\n' };

        public List<string> _RegisteredCmds = new List<string>();

        private SerialPort _serialPort = new SerialPort();

        private string _DeviceName;
        private string _ProcessorType;

        private ConcurrentQueue<string> _RxQueue;
        public ConcurrentQueue<string> _TxQueue = new ConcurrentQueue<string>();

        private Task _RxPump;
        private Task _TxPump;

        private ManualResetEvent mreAck = new ManualResetEvent(false);

        public bool IsOpen { get { return _serialPort.IsOpen; } }

        public COMDevice(string portName, ConcurrentQueue<string> RxQueue, Int32 baudRate = 500000)
        {
            _serialPort.PortName = portName;
            _serialPort.BaudRate = baudRate;
            _RxQueue = RxQueue;

            _serialPort.Handshake = Handshake.None;

            // format 8-N-1
            _serialPort.DataBits = 8;
            _serialPort.Parity = Parity.None;
            _serialPort.StopBits = StopBits.One;

            _serialPort.NewLine = "\n";

            _serialPort.ReadTimeout = 100;
            _serialPort.WriteTimeout = 100;
        }

        public bool Open()
        {
            try
            {
                _serialPort.Open();
                return true;
            }
            catch (TimeoutException)
            {
                if (_serialPort.IsOpen)
                    _serialPort.Close();
                return false;
            }
            catch (Exception)
            {
                if (_serialPort.IsOpen)
                    _serialPort.Close();
                return false;
            }
        }

        public bool Close()
        {
            if (!_serialPort.IsOpen)
                return false;

            try
            {
                _serialPort.Write("RESET\n");
                _serialPort.Close();

                Task[] tasks = { _TxPump, _RxPump };
                Task.WaitAll(tasks, 100);

                return true;
            }
            catch (Exception)
            {
                return false;
            }
        }

        private void ClearInputBuffer()
        {
            while (true)
            {
                int readCount = _serialPort.BytesToRead;
                if (readCount == 0)
                    break;
                byte[] buffer = new byte[readCount];
                _serialPort.Read(buffer, 0, readCount);
            }
        }
        
        public bool Initialize()
        {
            try
            {
                // clean receive buffer of Arduino and this application
                _serialPort.Write("\n");
                ClearInputBuffer();

                // get identification
                Debug.WriteLine($"{DateTime.Now.ToString("HH:mm:ss:fff")} Initialize: Write IDENT"); 
                _serialPort.Write("IDENT\n");

                _DeviceName = _serialPort.ReadLine();
                _ProcessorType = _serialPort.ReadLine();
                Debug.WriteLine($"{DateTime.Now.ToString("HH:mm:ss:fff")} Initialize: DeviceName: \"{_DeviceName}\" ProcessorType: \"{_ProcessorType}\"");

                // register the LED/Data commands
                _serialPort.Write("REGISTER\n");
                while (true)
                {
                    string sRegister = _serialPort.ReadLine();

                    if (sRegister == "")
                        break;
                    _RegisteredCmds.Add(sRegister);
                }
                Debug.WriteLine($"{DateTime.Now.ToString("HH:mm:ss:fff")} Initialize: {_RegisteredCmds.Count} Commands Registered");

                // start transmitting and receiving commands
                _TxPump = Task.Run(() => TxPumpAsync());
                _RxPump = Task.Run(() => RxPumpAsync());

                return true;
            }
            catch (Exception ex)
            {
                Debug.WriteLine($"{DateTime.Now.ToString("HH:mm:ss:fff")} Initialize: Exception {ex}");
                return false;
            }
        }

        private async Task RxPumpAsync()
        {
            var buffer = new byte[1024];
            StringBuilder sbCmd = new StringBuilder("", 256);

            try
            {
                while (_serialPort.IsOpen)
                {
                    int cnt = await _serialPort.BaseStream.ReadAsync(buffer, 0, 1024);

                    for (int i = 0; i < cnt; i++)
                    {
                        if ((char)buffer[i] == '\n')
                        {
                            if ((sbCmd.Length == 1) && (sbCmd[0] == 'A'))
                                // ACK sequence received
                                mreAck.Set();
                            else if (sbCmd.Length != 0)
                                // Command received
                                _RxQueue.Enqueue(sbCmd.ToString());
                            sbCmd.Clear();
                        }
                        else
                            sbCmd.Append((char)buffer[i]);
                    }
                }
            }
            catch (Exception)
            {
                Debug.WriteLine($"{DateTime.Now.ToString("HH:mm:ss:fff")} RxCommandsAsync: Exception");
            }
        }

        private async Task TxPumpAsync()
        {
            try
            {
                while (_serialPort.IsOpen)
                {
                    string sCmd;

                    if (_TxQueue.Count == 0)
                        continue;
                    else
                        _TxQueue.TryDequeue(out sCmd);

                    byte[] buffer = Encoding.ASCII.GetBytes($"{sCmd}\n");

                    int attempts = 2;

                    while (attempts-- != 0)
                    {
                        await _serialPort.BaseStream.WriteAsync(buffer, 0, sCmd.Length + 1);

                        // Reset the ManualResetEvent and wait for ACK for 50 msec
                        mreAck.Reset();
                        if (mreAck.WaitOne(50))
                        {
                            // free transmitter for next command
                            break;
                        }
                        else
                        {
                            Debug.WriteLine($"{DateTime.Now.ToString("HH:mm:ss:fff")} TxCommandsAsync: AckNOK attempt {2 - attempts}");
                            // Send linefeed to be sure we are in sync
                            await _serialPort.BaseStream.WriteAsync(LF, 0, 1);
                        }
                    }
                }
            }
            catch (Exception)
            {
                Debug.WriteLine($"{DateTime.Now.ToString("HH:mm:ss:fff")} TxCommandsAsync: Exception");
            }
        }
    }
}
\$\endgroup\$
4
  • \$\begingroup\$ Welcome to the Code Review community. We are very different from the Stack Overflow community, we want to see as much code as possible to help you improve your coding ability. We need to see at least complete functions and prefer complete classes including using statements. Please read our help pages starting with How do I ask a good question?. \$\endgroup\$
    – pacmaninbw
    Commented Mar 26, 2022 at 13:14
  • \$\begingroup\$ @pacmaninbw Thanks for the remark! I edited my question, and appended it with the full working code of my SerialPort class. \$\endgroup\$ Commented Mar 26, 2022 at 17:17
  • \$\begingroup\$ @HansBilliet You should consider to use CancellationToken to perform cooperative cancellation when the timeout has been reached. \$\endgroup\$ Commented Mar 28, 2022 at 8:27
  • \$\begingroup\$ @petercsala CancellationToken is not working very well with the async methods of the BaseStream. The token is only checked when entering the await, but is not being "cooperativeley" checked while waiting. Hence, the task is not cancelled. I finally dumped all the async/await stuff, and close the SerialPort, ignoring the Exception it generates, and wait for the tasks to RanForCompletion. You find the 2 versions of the code here: codereview.stackexchange.com/questions/275287/… \$\endgroup\$ Commented Mar 28, 2022 at 10:30

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Browse other questions tagged or ask your own question.