SerialPort.Write blocks when using multiple classes and separate COM ports

Silvan Zihlmann 20 Reputation points
2025-07-20T19:27:22.7033333+00:00

Hello dear community, After a long time, I’m reaching out to you again. Some time ago, I started a new coding project in WinUI3 that has gradually grown. Unfortunately, the code has developed quite quickly since I could only work on it in my limited free time and everything had to move fast. I'm not a trained computer scientist and I'm only doing this as a hobby, so the code isn't as clean or structured as it could be — I'm well aware of that.

Now to my actual problem: In my MainWindow code, I start two separate data broking processes, each in its own class and independently from one another. Both handle the data flow between MQTT and a COM port. Each class uses a different COM port, but both share the same MQTT topic.

When a new JSON message comes from MQTT, everything works fine through the broking logic. But as soon as it reaches SerialPort.Write(), the program hangs indefinitely and nothing gets written to the port.

I’ve already tried setting a timeout for the COM port, and while the program then continues, the data still doesn’t get written — the port simply times out. I’ve also verified that the serial port is indeed open at that point. A check with serialPort.IsOpen returns true.

I can’t explain the error, and I’ve reached the end of my knowledge and debugging efforts.

In the following link to my OneDrive you can find my whole project: StartFragment publicEndFragment

Thanks in advance

~Silvan

Developer technologies | Visual Studio | Debugging
{count} votes

Accepted answer
  1. James Wood 80 Reputation points
    2025-08-04T12:20:30.6833333+00:00

    When using multiple SerialPort instances across different classes, each tied to a separate COM port, and noticing that SerialPort.Write() is blocking, there are a few common culprits and solutions to consider:

    Potential Causes:

    1. Thread Contention or Blocking I/O

    Even though you're using different COM ports, if your write operations occur on shared threads (e.g., the UI thread or a single worker thread), one blocking write can stall the others.

    Fix: Make sure each SerialPort.Write() runs on its own dedicated thread or async task, especially if you're working with slow or unresponsive serial devices.

    1. SerialPort Handshake / Buffer Full

    If the receiving device isn't reading fast enough or uses flow control (handshake) like XON/XOFF or RTS/CTS, the write call can block until the buffer clears.

    Fix:

    • Disable handshake if not needed:

    Disable handshake if not needed:

    1. Shared Resources or Locks

    Are your serial port classes sharing any static variables, locks, or file-based resources? Unintended synchronization (like shared lock objects) could be introducing artificial contention.

    Fix: Ensure each serial class is self-contained and doesn't use shared locks unless intentional.

    1. Hardware/Driver Issues

    Some USB-to-Serial adapters (especially cheap ones) can cause system-level blocking, even on separate COM ports.

    Fix: Try with different adapters, or stagger write operations to test if hardware is the bottleneck.

    General Tips:

    Use BaseStream.WriteAsync() for non-blocking writes if you're on .NET Framework 4.5+ or .NET Core.

    Monitor port state with serialPort.IsOpen and handle exceptions like TimeoutException or IOException gracefully.

    Log start/end times of writes to debug blocking behavior in production.


    TL;DR:

    Even with separate ports and classes, blocking in SerialPort.Write often comes down to threading, handshake settings, or hardware limitations. Use async patterns, disable unnecessary flow control, and isolate each serial communication in its own thread or task.

    1 person found this answer helpful.
    0 comments No comments

2 additional answers

Sort by: Most helpful
  1. Gade Harika (INFOSYS LIMITED) 330 Reputation points Microsoft External Staff
    2025-07-25T07:07:39.8233333+00:00

    Thank you for details. I am not able to access the link.

    I hope the below solution helps

    When multiple classes or threads attempt to write to a SerialPort simultaneously, the Write method can block or freeze the application. This happens because SerialPort is not thread-safe. Simultaneous writes can overload the buffer and cause blocking. Uncoordinated access across classes leads to deadlocks or unpredictable behavior.

    To solve this, you can create a centralized SerialPortManager that owns a single SerialPort instance, queues all write operations, and uses a background thread to process writes sequentially. This ensures thread safety and prevents blocking.

    Here is the reusable code:

    SerialPortManager.cs

    Plain Text

    using System;
    using System.Collections.Concurrent;
    using System.IO.Ports;
    using System.Threading;
    
    public class SerialPortManager : IDisposable
    {
        private readonly SerialPort _serialPort;
        private readonly BlockingCollection<byte[]> _writeQueue = new();
        private readonly Thread _writeThread;
        private bool _disposed;
    
        public SerialPortManager(string portName, int baudRate = 9600)
        {
            _serialPort = new SerialPort(portName, baudRate)
            {
                WriteTimeout = 1000,
                ReadTimeout = 1000,
                DtrEnable = true,
                RtsEnable = true
            };
            _serialPort.Open();
    
            _writeThread = new Thread(WriteWorker) { IsBackground = true };
            _writeThread.Start();
        }
    
        private void WriteWorker()
        {
            foreach (var data in _writeQueue.GetConsumingEnumerable())
            {
                try
                {
                    _serialPort.Write(data, 0, data.Length);
                }
                catch (TimeoutException)
                {
                    Console.Error.WriteLine("Write timeout occurred.");
                }
                catch (InvalidOperationException)
                {
                    Console.Error.WriteLine("Serial port is closed.");
                }
                catch (Exception ex)
                {
                    Console.Error.WriteLine($"Serial write error: {ex.Message}");
                }
            }
        }
    
        public void WriteData(byte[] data)
        {
            if (!_disposed && data?.Length > 0)
                _writeQueue.Add(data);
        }
    
        public void Dispose()
        {
            if (_disposed) return;
            _disposed = true;
    
            _writeQueue.CompleteAdding();
            _writeThread.Join();
    
            if (_serialPort.IsOpen)
                _serialPort.Close();
    
            _serialPort.Dispose();
            _writeQueue.Dispose();
        }
    }
    

    DeviceA.cs

    Plain Text

    public class DeviceA
    {
        private readonly SerialPortManager _serial;
        public DeviceA(SerialPortManager serial) => _serial = serial;
    
        public void SendA() => _serial.WriteData(new byte[] { 0x01 });
    }
    

    DeviceB.cs

    Plain Text

    public class DeviceB
    {
        private readonly SerialPortManager _serial;
        public DeviceB(SerialPortManager serial) => _serial = serial;
    
        public void SendB() => _serial.WriteData(new byte[] { 0x02 });
    }
    

    Main Program

    Plain Text

    class Program
    {
        static void Main(string[] args)
        {
            using var serialManager = new SerialPortManager("COM1");
    
            var deviceA = new DeviceA(serialManager);
            var deviceB = new DeviceB(serialManager);
    
            deviceA.SendA();
            deviceB.SendB();
    
            Console.WriteLine("Data sent from both devices.");
            Console.ReadLine();
        }
    }
    

    This approach ensures that write operations are handled in the background, only one thread writes to the port, and the system is easy to extend and safely shut down. It also includes basic error handling for common serial port issues like timeouts or disconnections.

    Let me know if you need any help with this 

    1 person found this answer helpful.

  2. Varsha Dundigalla(INFOSYS LIMITED) 795 Reputation points Microsoft External Staff
    2025-07-25T06:52:10.3733333+00:00

    Thanks for the details. but I am unable to able to access the link.

    I hope the below solution may help.

    When multiple classes or threads attempt to write to a SerialPort simultaneously, the Write method can block or freeze the application. This happens because SerialPort is not thread-safe. Simultaneous writes can overload the buffer and cause blocking. Uncoordinated access across classes leads to deadlocks or unpredictable behavior.

    To solve this, you can create a centralized SerialPortManager that owns a single SerialPort instance, queues all write operations, and uses a background thread to process writes sequentially. This ensures thread safety and prevents blocking.

    SerialPortManager.cs

    using System;
    using System.Collections.Concurrent;
    using System.IO.Ports;
    using System.Threading;
    
    public class SerialPortManager : IDisposable
    {
        private readonly SerialPort _serialPort;
        private readonly BlockingCollection _writeQueue = new();
        private readonly Thread _writeThread;
        private bool _disposed;
    
        public SerialPortManager(string portName, int baudRate = 9600)
        {
            _serialPort = new SerialPort(portName, baudRate)
            {
                WriteTimeout = 1000,
                ReadTimeout = 1000,
                DtrEnable = true,
                RtsEnable = true
            };
            _serialPort.Open();
    
            _writeThread = new Thread(WriteWorker) { IsBackground = true };
            _writeThread.Start();
        }
    
        private void WriteWorker()
        {
            foreach (var data in _writeQueue.GetConsumingEnumerable())
            {
                try
                {
                    _serialPort.Write(data, 0, data.Length);
                }
                catch (TimeoutException)
                {
                    Console.Error.WriteLine("Write timeout occurred.");
                }
                catch (InvalidOperationException)
                {
                    Console.Error.WriteLine("Serial port is closed.");
                }
                catch (Exception ex)
                {
                    Console.Error.WriteLine($"Serial write error: {ex.Message}");
                }
            }
        }
    
        public void WriteData(byte[] data)
        {
            if (!_disposed && data?.Length > 0)
                _writeQueue.Add(data);
        }
    
        public void Dispose()
        {
            if (_disposed) return;
            _disposed = true;
    
            _writeQueue.CompleteAdding();
            _writeThread.Join();
    
            if (_serialPort.IsOpen)
                _serialPort.Close();
    
            _serialPort.Dispose();
            _writeQueue.Dispose();
        }
    }
    

    DeviceA.cs

    public class DeviceA
    {
        private readonly SerialPortManager _serial;
        public DeviceA(SerialPortManager serial) => _serial = serial;
    
        public void SendA() => _serial.WriteData(new byte[] { 0x01 });
    }
    

    DeviceB.cs

    public class DeviceB
    {
        private readonly SerialPortManager _serial;
        public DeviceB(SerialPortManager serial) => _serial = serial;
    
        public void SendB() => _serial.WriteData(new byte[] { 0x02 });
    }
    

    Main Program

    class Program
    {
        static void Main(string[] args)
        {
            using var serialManager = new SerialPortManager("COM1");
    
            var deviceA = new DeviceA(serialManager);
            var deviceB = new DeviceB(serialManager);
    
            deviceA.SendA();
            deviceB.SendB();
    
            Console.WriteLine("Data sent from both devices.");
            Console.ReadLine();
        }
    }
    

    This approach ensures that write operations are handled in the background, only one thread writes to the port, and the system is easy to extend and safely shut down. It also includes basic error handling for common serial port issues like timeouts or disconnections.

    Let us know if you need any help with this.


Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.