2
\$\begingroup\$

Here I have some complete working code but some aspects seem questionable to me. What I have done is written a wrapper for the Microsoft serial port (System.IO.Ports.SerialPort) in C#. Essentially what the serial port wrapper class does is wrap the event handling mechanism to streamline how we utilize serial ports in our designs. The serial port wrapper contains an event handling mechanism such that whenever the DataReceived event gets handled by the serial port, the incoming data is read and stored in a buffer that exists as a private member to the wrapper class.

My main concern is how data is pulled from aforementioned buffer whenever the string Receive(int timeout) method is called. My goal with this was to internalize the cancellation mechanism into the wrapper. The idea with this class' implementation is that all receiving operations should be externally cancelable via a call to the Cancel method. The idea here is that Receive would be called on a separate thread which is blocking. Then, any other thread could call on Cancel to un-block the receiving method.

What I would especially like to know about the Receive methods is... are there any performance implications that accompany creating/passing tokens? Especially, if I had a thread going that was calling Receive in a loop maybe? Say I have some thread that's calling Receive in a loop because I am looking to receive some certain string (such as an end-of-line signal), could I potentially have a problem?

/// <summary>
/// A wrapper for the Microsoft serial port that can be passed into
/// objects and utilized through a common communication interface.
/// </summary>
/// <remarks>Adheres to the common communication interface so that we
/// can keep the application code agnostic to the communication
/// medium. This will make things simpler for when/if we decide to switch
/// to Ethernet or GPIB or any other future communication mediums.</remarks>
public class SerialPort : ISerialPort, IDisposable
{
    #region Private Members
    private ILogger log;
    private CancellationTokenSource rxSource = new CancellationTokenSource();
    private BlockingCollection<string> buffer = new BlockingCollection<string>();
    private System.IO.Ports.SerialPort port = new System.IO.Ports.SerialPort();
    #endregion Private Members
    #region Constructors
    /// <summary>
    /// Creates an instance of a serial-port with a logging library, and a baud-rate.
    /// </summary>
    /// <param name="log">An interface to some logging library.</param>
    /// <param name="baudRate">Maximum communication rate (bits/second).</param>
    public SerialPort(ILogger log, int baudRate)
    {
        // initialize the serial port settings
        this.port.WriteTimeout = 1000;
        this.port.Handshake = Handshake.None;
        this.port.DtrEnable = true;
        this.port.RtsEnable = true;
        this.port.BaudRate = baudRate;
        this.port.DataBits = 8;
        this.port.StopBits = StopBits.One;
        this.port.Parity = Parity.None;
        this.port.ReceivedBytesThreshold = 1;

        // inject 
        this.log = log;
    }
    /// <summary>
    /// Creates an instance of a serial-port with a baud-rate, and stop-bits.
    /// </summary>
    /// <param name="log">An interface to some logging library.</param>
    /// <param name="baudRate">Maximum communication rate (bits/second).</param>
    /// <param name="stopBits">The number of stop bits used on the serial-port.</param>
    public SerialPort(ILogger log, int baudRate, int stopBits)
    {
        // initialize the serial port settings
        this.port.WriteTimeout = 1000;
        this.port.Handshake = Handshake.None;
        this.port.DtrEnable = true;
        this.port.RtsEnable = true;
        this.port.BaudRate = baudRate;
        this.port.DataBits = 8;
        this.port.StopBits = (StopBits)stopBits;
        this.port.Parity = Parity.None;
        this.port.ReceivedBytesThreshold = 1;

        // inject 
        this.log = log;
    }
    #endregion Constructors
    #region Public Interface Properties
    public string[] AvailablePortNames
    {
        get
        {
            string[] ports = null;

            // get a list of available ports
            ports = System.IO.Ports.SerialPort.GetPortNames();

            if (ports.Length == 0)
            {
                return null;
            }
            else
            {
                return ports;
            }
        }
    }
    public string Name
    {
        get
        {
            return this.port.PortName;
        }

        set
        {
            int tries = 5;

            // there is always a chance that the port is open
            // if trying some operations back-to-back
            // give it a few extra tries if necessary
            for (int i = tries; i > 0; --i)
            {
                try
                {
                    if (!this.port.IsOpen)
                    {
                        this.port.PortName = value;
                        break;
                    }
                }
                catch (UnauthorizedAccessException e)
                {
                    this.log.Error(e);
                }
            }
        }
    }
    #endregion Public Interface Properties
    #region Public Interface Methods
    public bool Disconnect()
    {
        bool success = false;

        try
        {
            // unsubscribe as to stop receiving any data
            this.port.DataReceived -= this.DataReceived;

            // unblock anyone who is waiting on the buffer
            this.Cancel();

            // disconnect the port
            this.port.Close();

            // test the results
            success = !this.port.IsOpen;
        }
        catch (UnauthorizedAccessException e)
        {
            this.log.Error(e);
        }
        catch (InvalidOperationException e)
        {
            this.log.Error(e);
        }

        return success;
    }
    public bool Transmit(string data)
    {
        bool isTransmitted = false;

        try
        {
            if (this.port.IsOpen)
            {
                // transmit the data
                this.port.Write(data);

                // flag that we've written data to the port
                isTransmitted = true;
            }
            else
            {
                this.log.Error("Cannot transmit anything on " + this.port.PortName + ", it is not open.");
            }
        }
        catch (InvalidOperationException e)
        {
            this.log.Error(e);
        }
        catch (UnauthorizedAccessException e)
        {
            this.log.Error(e);
        }
        catch (IOException e)
        {
            this.log.Error(e);
        }
        catch (TimeoutException e)
        {
            this.log.Error(e);
        }

        return isTransmitted;
    }
    public bool Transmit(char data)
    {
        bool isTransmitted = false;

        // transmit the character as a string
        isTransmitted = this.Transmit(new string(data, 1));

        return isTransmitted;
    }
    public string Receive(int timeOut)
    {
        CancellationToken t;
        string data = string.Empty;

        try
        {
            // attempt to read the buffer
            if (this.rxSource != null)
            {
                // create the token
                t = this.rxSource.Token;

                // start reading/blocking on the buffer
                this.buffer.TryTake(out data, timeOut, t);
            }
        }
        catch (OperationCanceledException) { }

        return data;
    }
    public string Receive()
    {
        CancellationToken t;
        string data = string.Empty;

        try
        {
            // attempt to read the buffer
            if (this.rxSource != null)
            {
                // create the token
                t = this.rxSource.Token;

                // start reading/blocking on the buffer
                data = this.buffer.Take(t);
            }
        }
        catch (OperationCanceledException) { }

        return data;
    }
    public bool Connect()
    {
        bool success = false;
        int tries = 5;

        for (int i = tries; i > 0; --i)
        {
            try
            {
                if (!this.port.IsOpen)
                {
                    // subscribe to the event-data
                    this.port.DataReceived += this.DataReceived;

                    // open up the port
                    this.port.Open();
                    success = true;
                    break;
                }
            }
            catch (IOException e)
            {
                this.log.Error(e);
            }
            catch (UnauthorizedAccessException e)
            {
                this.log.Error(e);
            }
        }

        return success;
    }
    public void Cancel()
    {
        this.rxSource?.Cancel();
        this.rxSource = new CancellationTokenSource();
    }
    #endregion Public Interface Methods
    #region Private Methods
    /// <summary>
    /// This method will retrieve the data that has been received from the.
    /// </summary>
    /// <param name="sender">The serial port on which the data was received.</param>
    /// <param name="newDataReceivedEvent">The act of receiving data.</param>
    private void DataReceived(object sender, SerialDataReceivedEventArgs newDataReceivedEvent)
    {
        // establish the serial port as the sender
        System.IO.Ports.SerialPort p = sender as System.IO.Ports.SerialPort;
        string data = string.Empty;

        try
        {
            if (p.IsOpen)
            {
                data = p.ReadExisting();
                if (!string.IsNullOrEmpty(data))
                {
                    this.buffer.Add(data);
                }
            }
            else
            {
                this.log.Error("Failed to read from " + (sender as System.IO.Ports.SerialPort).PortName + " because it is not open.");
            }
        }
        catch (InvalidOperationException e)
        {
            this.log.Error(e);
        }
        catch (IOException e)
        {
            this.log.Error(e);
        }
    }
    #endregion Private Methods
    #region IDisposable Support
    private bool disposedValue = false; // To detect redundant calls
    protected virtual void Dispose(bool disposing)
    {
        if (!disposedValue)
        {
            if (disposing)
            {
                // TODO: dispose managed state (managed objects).
            }

            // free unmanaged resources (unmanaged objects) and override a finalizer below.
            this.buffer?.Dispose();
            this.rxSource?.Dispose();
            this.port?.Dispose();

            // set large fields to null.
            this.buffer = null;
            this.rxSource = null;
            this.port = null;

            disposedValue = true;
        }
    }

    // override a finalizer only if Dispose(bool disposing) above has code to free unmanaged resources.
    ~SerialPort()
    {
        // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
        Dispose(false);
    }

    // This code added to correctly implement the disposable pattern.
    public void Dispose()
    {
        // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
        Dispose(true);

        // uncomment the following line if the finalizer is overridden above.
        GC.SuppressFinalize(this);
    }
    #endregion IDisposable Support
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.