Due to requirements / requests from a company I work with I have been coding more and more in C# lately. Along the way I have learnt a number of new concepts / techniques.
Recently I have coded three applications, an “Antenna Tester” that will repeatedly query the dBm signal on a serial GSM modem, a “Load Tester” to simultaneously establish and maintain many hundreds of connections to a server using a specific custom protocol, and an “AT Command Sender” that can program (send a specified set or commands) repeatedly upto 8 devices connected via serial.
The Antenna Tester was the simplest and most basic of the 3 applications. It used techniques and classes I am already somewhat familiar with. Application basically starts/stops a single thread using the BackgroundWorker Class in which it repeatedly queries the serial port using the SerialPort Class. Query interval can be specified, and the average signal strength is displayed.
BackgroundWorker is a useful class to use over others such as the more direct “Thread” class because it provides several helpful extras such as:
- “CancelAsync() ” / “CancellationPending” – Used to signal cancelation / check for cancelation (Note: You need to actually check for “CancellationPending == True” in your application logic at any point that blocks for a significant point of time, and break/return if it is found therefore allowing the Background Worker to end).
- “ReportProgress() – Used to pass though e.g. a percentage value to update a Progress Bar, text to show in an onscreen log, or both.
- “RunWorkerCompleted()” – Used to signal the background thread has finished or completed which can be used to reset your form i.e. reenable locked controls.
These extras are required since when using a separate thread ordinarily you loose the ability to directly interact with the form and otherwise would receive “Cross-thread operation not valid” errors from the compiler.
In addition “Invoke” was also used to update the average, last signal values etc on the form (to get around cross thread issues). ReportProgress could also likely have been used but in this case Invoke seemed more logical.
The load tester was more complex code wise than the antenna tester, and used the “TcpClient Class” for network based communications instead of “SerialPort Class” however most of the extra complexity was just due to parsing and responding to the custom protocol.
The only major new concept was not just using BackgroundWorker alone, but using BackgroundWorker to launch separate threads with the “Thread” class. Each socket connection runs from its own thread.
Using BackgroundWorker to launch threads means the main form application does not block. Using a single connection per thread simplifies coding in terms of managing application states.
AT Command Sender
Some lessons learnt:
- Only code running from the form class can call Invoke to update the GUI, Invoke can not be called from other classes. E.g. have a method “updateDisplay()” in the main form class. [See code sample 1]
- To call a method from the main form class (required since this is the only class that can use Invoke) you need to pass through the form class to the thread. [See code sample 2]
- It is a good idea to “Join” each thread after you end it, this will cause the GUI to block until the thread has ended, which usually should not be very long (as long as you are checking if the thread needs to return whenever there is the potential for a delay). The GUI blocking is not ideal, but probably better than the alternative of potentially leaving the user with zombie processing running in the background if something does go wrong.
- It is advisable to add a “FormClosing” event and use it to also close / Join running threads in case the user uses the “[X]” or otherwise closes the application without disconnecting in the normal way with the specifically coded button for that purpose.
- There is the potential for crashes on exit if the form is closed with background processes still running which subsequently try to update the GUI, this situation needs to be handled / avoided, i.e. ensure the thread checks if the application is exiting before trying to update the GUI.
- If you have a GUI with repeating sets of e.g. labels, dropdowns etc there does not seem to be any easy / straight forward way to design it once, and replicate it. Instead manually creating each, and then saving references to an array seems like the best option, and then allows easy looping over them all for perform required operations. [See code sample 3]