In the first article, I believe that many people can do this without reading it. However, anyone who has used the Microsoft SerialPort class has encountered this embarrassment. When the serial port is closed, the software will be deadlocked. Oh, my God, I am not so arrogant. Forget it. Don't be so absolute. 99.9% of people have encountered this problem. I think only half of them actually solve the problem. The other half closed their eyes and hoped that this problem would not happen to customers.
If you see my article, you can rest assured that the problem is saved. Let's review the code in the previous article.
Void comm_datareceived (Object sender, serialdatareceivedeventargs e) <br/>{< br/> // record it first to avoid some reason, manual reasons, and long operation time between several times, inconsistent cache <br/> int n = comm. bytestoread; <br/> // declare a temporary array to store the current Serial Port Data <br/> byte [] Buf = new byte [N]; <br/> // increase the receipt count <br/> received_count + = N; <br/> // read the buffered data <br/> comm. read (BUF, 0, n); <br/> // clear the content of the string constructor <br/> builder. clear (); <br/> // to access the UI resources, you must use the invoke method to synchronize the UI. <Br/> This. invoke (eventhandler) (delegate {... interface update, omitted}); <br/>}</P> <p> private void buttonopenclose_click (Object sender, eventargs E) <br/>{< br/> // judge the operation based on the current serial port object <br/> If (Comm. isopen) <br/>{< br/> // when you click it, the serial port is closed <br/> comm. close (); // where the software may die <br/>}< br/> else <br/> {...} <br/>}
Why is the deadlock? concurrency conflicts.
Let's take a look at the implementation of SerialPort and the serial communication mechanism. When you open the serial port, SerialPort will create a listening thread listenthread, waiting for the serial port to be registered to be interrupted, when an interruption is received, the datareceived event is called. After the call is complete, continue to wait until the serial port is closed and exited.
How does the main UI thread do this? First create a form and then execute application. Run (form instance ). In this case, application. Run creates a message loop to process related messages cyclically.
Here we have two threads, the main UI thread and the serial port listening thread. When datareceived processes data, you need to synchronize threads to avoid concurrency conflicts. What is concurrency conflict? A concurrency conflict occurs when two or more parallel (at least seemingly) threads run, multiple threads operate on the resources of a thread together, in terms of timing or not operating in the expected sequence, this may lead to disordered data or wait for each other to complete the deadlock software.
Most serial programs are the latter. Why? Let's take a look at what datareceived has done in our example? Read the data first, and then call this. invoke to update the UI. Here, when invoke is used, the listening thread will wait for the mark of the UI thread, and then start to operate the UI resources. Before the operation is complete, the listening thread will stop calling the datareceived method, if at this time. What if I disable the serial port concurrently? The close method of the SerialPort will first try to wait for a mutex, critical section, or event like the listening thread (not sure which one is used for. net ). When will this synchronization object be released? Release at the end of each loop. Why does the loop not end? Because after this cyclic operation is executed to datareceived and the invoke is executed to update the interface, why is the invoke not completed? Several lines of code seem simple. Although I have not carefully studied it. net's invoke principle, but I guess it is synchronized through the message method, which is why so many classes, only controls (forms are also a type of control ,. in terms of concept, net subverts Microsoft's own concept. The traditional Win32 programming means that all controls are windows, but the parent form is different and the display form is different, however, they are all based on system message queues ,. net is out of higher abstraction, which is the opposite. The invoke method is available. (Delegate your own invoke to be different from this one)
I guess that the invoke of the control/form is implemented in sendmessage mode. After a message is sent, it will wait for the message loop to process the message. If you close the serial port directly. The button itself will be converted to the Message wm_click. When the message loop is processing the wm_click button, The onclick method of your button is called to trigger the call of your buttonclose_click event, this is a synchronous call. Your main thread stops the click event during message processing, and your click event calls the close method of SerialPort, the close method is associated with the synchronous semaphores of the serial listener thread and needs to wait for a while to end. The while loop calls the datareceived method, which calls invoke, that is, a message is sent to the Message Queue to wait for the result, but the message loop is processing your close button event and waiting to exit.
It is too complicated. In this case, if you want to close the serial port successfully, you need to call the datareceived method in the while to release the synchronization signal. After invoke is executed, You need to execute the message loop, fortunately, we have a way to execute a message loop to break the deadlock. Application. doevents (). Fortunately, unfortunately, we are lucky. However, the problem is that you can end invoke, but you cannot determine whether your request will not be concurrent at any time after you call the message loop, maybe the serial operation of a single CPU is simulated in parallel, and the time slice is first allocated to the serial port listening thread with a higher priority? It is possible. Therefore, we need a method to avoid invoking the form again. The following is an example of no driver after optimization. we modify the datareceived method, disable the method, and define two tags: listening and closing.
Using system; <br/> using system. collections. generic; <br/> using system. componentmodel; <br/> using system. data; <br/> using system. drawing; <br/> using system. LINQ; <br/> using system. text; <br/> using system. windows. forms; <br/> using system. io. ports; <br/> using system. text. regularexpressions; <br/> namespace serialportsample <br/>{< br/> Public partial class serialportsampleform: Form <br/>{< br/> PRI Vate SerialPort comm = new SerialPort (); <br/> private stringbuilder builder = new stringbuilder (); // avoid repeated creation in the event processing method and define it outside. <Br/> private long received_count = 0; // receipt count <br/> private long send_count = 0; // sending count <br/> private bool listening = false; // whether the invoke-related operation has not been completed <br/> private bool closing = false; // whether the serial port is closed and the application is executed. doevents and prevent the next invoke <br/> Public serialportsampleform () <br/>{< br/> initializecomponent (); <br/>}< br/> // form initialization <br/> private void form1_load (Object sender, eventargs E) <br/>{< br/> // initialize the drop-down serial port Name list box <br/> string [] ports = SerialPort. getportnames (); <br/> array. sort (ports); <br/> comboportname. items. addrange (ports); <br/> comboportname. selectedindex = comboportname. items. count> 0? 0:-1; <br/> combobaudrate. selectedindex = combobaudrate. items. indexof ("9600"); <br/> // initialize the SerialPort object <br/> comm. newline = "/R/N"; <br/> comm. rtsenable = true; // depending on the actual situation. <Br/> // Add event registration <br/> comm. datareceived + = comm_datareceived; <br/>}< br/> void comm_datareceived (Object sender, serialdatareceivedeventargs e) <br/> {<br/> If (closing) Return; // If the listener is shutting down, ignore the operation and return directly. a loop of the listener thread is completed as soon as possible. <br/> try <br/>{< br/> listening = true; // set the tag, indicating that I have started to process the data and will use the system UI later. <Br/> int n = comm. bytestoread; // record it first to avoid some reason, such as human reasons, which may take a long time between operations, inconsistent cache <br/> byte [] Buf = new byte [N]; // declare a temporary array to store the current Serial Port Data <br/> received_count + = N; // increase the receipt count <br/> comm. read (BUF, 0, n); // read the buffered data <br/> builder. clear (); // clear the content of the string constructor <br/> // to access the UI resources, use invoke to synchronize the UI. <Br/> This. invoke (eventhandler) (DeleGate <br/>{< br/> // determines whether it is displayed as 16 forbidden <br/> If (checkboxhexview. checked) <br/>{< br/> // concatenates a hex string in sequence <br/> foreach (byte B in BUF) <br/>{< br/> builder. append (B. tostring ("X2") + ""); <br/>}< br/> else <br/> {<br/> // convert the string to a string based on ASCII rules. <br/> builder. append (encoding. ASCII. getstring (BUF); <br/>}< br/> // append to the end of the text box and scroll to the end. <Br/> This. txget. appendtext (builder. tostring (); <br/> // modify the receipt count <br/> labelgetcount. TEXT = "Get:" + received_count.tostring (); <br/> })); <br/>}< br/> finally <br/> {<br/> listening = false; // when I use up, the UI can close the serial port. <Br/>}< br/> private void buttonopenclose_click (Object sender, eventargs e) <br/>{< br/> // based on the current serial port object, to determine the operation <br/> If (Comm. isopen) <br/>{< br/> closing = true; <br/> while (Listening) application. doevents (); <br/> // when opening, click to close the serial port <br/> comm. close (); <br/> closing = false; <br/>}< br/> else <br/>{< br/> // when closing, set the port and enable the port after the baud rate <br/> comm. portname = comboportname. text; <br/> comm. baudrate = I NT. parse (combobaudrate. text); <br/> try <br/> {<br/> comm. open (); <br/>}< br/> catch (exception ex) <br/>{< br/> // capture exception information, create a new comm object, which is unavailable before. <Br/> comm = new SerialPort (); <br/> // send the actual exception information to the customer. <Br/> MessageBox. show (ex. message); <br/>}< br/> // set the button status <br/> buttonopenclose. TEXT = comm. isopen? "Close": "Open"; <br/> buttonsend. enabled = comm. isopen; <br/>}< br/> // dynamic modification to obtain whether the text box supports automatic line feed. <Br/> private void checkboxnewlineget_checkedchanged (Object sender, eventargs e) <br/>{< br/> txget. wordwrap = checkboxnewlineget. checked; <br/>}< br/> private void buttonsend_click (Object sender, eventargs e) <br/>{< br/> // define a variable, the record sent several bytes <br/> int n = 0; <br/> // hexadecimal transmission <br/> If (checkboxhexsend. checked) <br/>{< br/> // we ignore the rules. If something is wrong, we allow only regular expressions to get a valid hexadecimal number <br/> matchcollection MC = RegEx. Matches (txsend. Text ,@"(? I) [/DA-F] {2} "); <br/> List <byte> Buf = new list <byte> (); // fill in the temporary list <br/> // Add it to the list in sequence <br/> foreach (Match m in MC) <br/>{< br/> Buf. add (byte. parse (M. value); <br/>}< br/> // convert the list to an array and send it <br/> comm. write (BUF. toarray (), 0, Buf. count); <br/> // record the number of bytes sent <br/> N = Buf. count; <br/>}< br/> else // the ASCII code is sent directly <br/>{< br/> // contains the line break <br/> If (checkboxnewlinesend. checked) <br/>{< br/> comm. writeline (txsend. Text); <br/> N = txsend. text. length + 2; <br/>}< br/> else // does not contain line breaks <br/>{< br/> comm. write (txsend. text); <br/> N = txsend. text. length; <br/>}< br/> send_count + = N; // accumulate the number of sent bytes <br/> labelsendcount. TEXT = "Send:" + send_count.tostring (); // update interface <br/>}< br/> private void buttonreset_click (Object sender, eventargs E) <br/>{< br/> // reset the number of bytes that are accepted and sent to the counter and update the interface. <Br/> send_count = received_count = 0; <br/> labelgetcount. TEXT = "Get: 0"; <br/> labelsendcount. TEXT = "Send: 0"; <br/>}< br/>
So far, the deadlock will no longer be closed.
I hope this article can solve your urgent needs. I am very happy to share with you the problems that most people have encountered at our layer. If you do not understand it, you are welcome to discuss it.
Subsequent articles on the underlying design of communication programs will show a universal communication library with rich scalability but design introduction, supporting network, Bluetooth, serial communication, and parallel communication. But don't expect me to implement it. I just designed this framework.
Sample Code
// Append by wuyazhe @ 2011-5-26
There is a slight omission above. It is from the first article. The result is still not modified here. There is a line in the source code and you need to modify it.
// Sending button
Buf. Add (byte. parse (M. Value ));
To change
Buf. Add (byte. parse (M. Value, system. Globalization. numberstyles. hexnumber ));