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");
}
}
}
}
CancellationToken
to perform cooperative cancellation when the timeout has been reached. \$\endgroup\$