1

I am writing a program that detects when a USB to serial device is removed from or attached to the PC. When a device is removed, it pauses a method that asynchronously reads from that serial port. When reconnected, the async method begins reading again.

The serial port functions all work, as does the detection code, but the detection event fires up to five or six times, causing a little confusion for the program temporarily.

This is the event code:

public Form1()
    {
    InitializeComponent();

        // Check to see if USB Serial cable has been plugged or unplugged
        SerialPortService.PortsChanged += (sender1, changedArgs) => DetectChange(changedArgs.EventType);
    }
    
    static SerialPortService()
    {
        _serialPorts = GetAvailableSerialPorts();  // Get valid serial ports
        MonitorDeviceChanges();
    }

    public static void CleanUp()
    {
        arrival.Stop();
        removal.Stop();
    }

    public static event EventHandler<PortsChangedArgs> PortsChanged;

    private static void MonitorDeviceChanges()
    {
        try
        {
            var deviceArrivalQuery = new WqlEventQuery("SELECT * FROM Win32_DeviceChangeEvent WHERE EventType = 2");
            var deviceRemovalQuery = new WqlEventQuery("SELECT * FROM Win32_DeviceChangeEvent WHERE EventType = 3");

            arrival = new ManagementEventWatcher(deviceArrivalQuery);
            removal = new ManagementEventWatcher(deviceRemovalQuery);

            arrival.EventArrived += (o, args) => RaisePortsChangedIfNecessary(global::IPPusher.EventType.Insertion);
            removal.EventArrived += (sender, eventArgs) => RaisePortsChangedIfNecessary(global::IPPusher.EventType.Removal);

            // Start listening for events
            arrival.Start();
            removal.Start();
        }
        catch (ManagementException err)
        {
            MessageBox.Show("Management exception = " + err, "Info", MessageBoxButtons.OK);
        }
    }
    
    private static void RaisePortsChangedIfNecessary(EventType eventType)
    {
        if (eventType == global::IPPusher.EventType.Insertion)
        {
            Thread.Sleep(1000);  // sleep while the PC sorts out its COM ports
            var availableSerialPorts = GetAvailableSerialPorts();
            var added = availableSerialPorts.Except(_serialPorts).ToArray();
            var portInUse = availableSerialPorts.Max();
            
            Form1.IsPaused = false;
            PortsChanged.Raise(null, new PortsChangedArgs(eventType, added));  // Fire the insertion event

        }
        if (eventType == global::IPPusher.EventType.Removal)
        {
            serialMethods.CloseSerialPort(Form1.PortName); // close active serial port
            
            Thread.Sleep(1000);  // sleep to sort things out
            var availableSerialPorts = GetAvailableSerialPorts();
                        
            Form1.IsPaused = true;  // pause the async method
            var removed = _serialPorts.Except(availableSerialPorts).ToArray();
            _serialPorts = availableSerialPorts;
            PortsChanged.Raise(null, new PortsChangedArgs(eventType, removed));  // fire the removal event
        }
    }
}

public enum EventType
    {
        Insertion,
        Removal,
    }

public class PortsChangedArgs : EventArgs
    {
        private readonly EventType _eventType;

        private readonly string[] _serialPorts;

        public PortsChangedArgs(EventType eventType, string[] serialPorts)
        {
            _eventType = eventType;
            _serialPorts = serialPorts;
        }

        public EventType EventType => _eventType;
    }
}

This works, but I have no idea how to consume the event so that it only fires once for each state. As I said, it causes a little hiccup in the app and I lose some data from the serial port. Looking around at stack overflow, I see that I should unsubscribe and then re-subscribe to the event. I'm not sure where to put that code.

Any help would be greatly appreciated.

Zoot
  • 21
  • 3
  • Does this help at all? https://stackoverflow.com/questions/75476494/generalized-c-sharp-method-for-handling-an-event-once-and-then-unsubscribing – Emperor Eto Feb 25 '23 at 20:48
  • That "little confusion" is normal, you can't remove a serial port that is in use. Backgrounder [is here](https://stackoverflow.com/a/9837330/17034). – Hans Passant Feb 25 '23 at 20:54
  • Thank you both. Sorry it took so long to reply (blew up the CPU on my PC). I will try the link from Just Answer the Question and see where that takes me. I suspect that it will help. – Zoot Mar 11 '23 at 17:32

0 Answers0