AdorableVB
AdorableVB

Reputation: 1393

Unseen bug, application stucks

My app is about 95% completed and now I am on a testing phase..
I used the .exe file in debug folder then I use the app to save a specific line from a burst of string from a gps module to access database..
Here's the flow

Sample Data
enter image description here
from gprmc to gpgsa = 1second interval. So technically module sends three lines or so every second.

Try  
Dim fruit As String = "$GPRMC"

            For Each line As String In RichTextBox1.Lines
                If line.Contains(fruit) Then
                    ProgressBar1.Value = 0
                    txt = line.Split(","c)
                    Insert()
                End If
            Next
Catch ex As Exception
        sPort.Close()
        MessageBox.Show("There had been no data received.", Me.Text, MessageBoxButtons.RetryCancel)
        Call btnStartTimer_Click(sender, New EventArgs)
    End Try

Under Minute_Tick, every 60sec, Second.Start (which is the above code). Every minute, I clear the RichTextBox then after a 2 seconds (rtb is filled again) I get the line with $GPRMC in it and .Split it to an array ~ txt(). Henceforth I have the words separated by coma in an array.

Now I add it to the database :

' Now inside a Using block
           If txt(3) = String.Empty Then
                .AddWithValue("@lat", 0)
            Else
                Dim la As Double = Double.Parse(txt(3).Substring(0, 2)) + Double.Parse(txt(3).Substring(2)) / 60.0
                .AddWithValue("@lat", la)
            End If

txt(3) is the Latitude DMS format, so I added a little converting snippet. It works fine, though I added a condition to make sure it will not compute for a null or 0 value.

UPDATE (I can't really understand this code, because I just copied it, but it works fine with the last app I created.)

Delegate Sub SetTextCallback(ByVal [text] As String)
Dim x As New SetTextCallback(AddressOf ReceivedText)
Private Sub SerialPort1_DataReceived(ByVal sender As Object, ByVal e As System.IO.Ports.SerialDataReceivedEventArgs) Handles sPort.DataReceived
    ReceivedText(sPort.ReadExisting)
End Sub
Private Sub ReceivedText(ByVal [text] As String)
    If Me.RichTextBox1.InvokeRequired Then
        Me.Invoke(x, New Object() {(text)})
    Else
        Me.RichTextBox1.Text &= [text]
    End If
End Sub

Troubles I found
I am experiencing something like a Threading Sleep. The app hangs, can't click, can't close, but its "running" on the task manager, so I think it is in an infinite loop, or am I wrong? I ended closing it on task manager, and open it again. Runs smooth.. then later, it will stuck.
I was thinking and until now, what causes it? I thought its because the module cannot always get a signal, so it returns nothing.. but it saves 0 in the DB (code above) if the module did not gave coordinates, so there is no problem in that.

Can you guys help me the culprit? I am on my third day so I decided that I need some help. thanks. Let me know if you need anything or some clarifications.

Upvotes: 0

Views: 196

Answers (1)

Hans Passant
Hans Passant

Reputation: 941317

If Me.RichTextBox1.InvokeRequired Then
    Me.Invoke(x, New Object() {(text)})
Else
    Me.RichTextBox1.Text &= [text]
End If

Your code is doing this:

enter image description here

This is a firehose problem. The issue started with the SerialPort.ReadExisting() call. That normally returns one or two characters, serial ports are pretty slow. At a common Baudrate setting of 9600 baud, you get 1000 characters per second so you effectively add new text to the RichTextBox about 500 times per second, give or take.

This forces the RichTextBox to re-allocate its internal buffer that stores the text and make room for the extra added characters, then copy all of the existing text from the old buffer into the new buffer and append the new text. Then update the screen.

This goes pretty smoothly when you first start your program, the RichTextBox doesn't contain much text yet. But gets progressively more expensive, more and more characters have to be copied.

Until you reach a critical point where the copying starts to take too much time, more time than the rate at which you call Me.Invoke(). The UI thread now starts to fall behind, never being able to keep up. Like trying to drink from a fire-hose. As soon as it is done copying the internal buffer, yet another invoke request needs to be dispatched, forcing the buffer to be reallocated again.

The UI thread now stops taking care of its normal lower-priority duties. Which includes updating the screen and handling input events. You notice this from your program acting frozen, as though it has deadlocked. Windows replaces your main window with the ghost window that says "Not responding" and banging on the mouse or keyboard has no effect. All you can do is terminate the program with the debugger or Task Manager and restart it. Which works fine, the RichTextBox again has an empty buffer and copying is cheap again.

Do note how this is a general problem when handling strings. The .NET Framework has the StringBuilder class to solve it. That however isn't available for RichTextBox, you need to find a different solution.

so you effectively add new text to the RichTextBox about 500 times per second

You need to tackle that problem. It is pointless to add text to the RTB at that rate, no human can ever observe such a high rate. Adding text 20 times per second is already plenty fast enough, it starts to look like a blur when you do it faster than that. Or in other words, right now you doing it 25x faster than necessary. Giving both the RTB and the UI thread a hard time keeping up with that rate.

Also note that this explain your parsing problem. You are parsing an incomplete line of text. So a first-order fix for this is to only invoke when you get a complete line of text from the serial port. That's very easy to come by:

Private Sub SerialPort1_DataReceived(ByVal sender As Object, ByVal e As System.IO.Ports.SerialDataReceivedEventArgs) Handles sPort.DataReceived
    ReceivedText(sPort.ReadLine)
End Sub

In other words, you ask the serial port to return an entire line of text instead of just one or two characters. This automatically greatly lowers the invoke rate and solves your parsing problem.

Two more thing you have to do. Your program will still hang when it runs long enough. You really do have to limit the amount of text in the RTB. Just throw half of it away when it stores more than, say, 65000 characters. And you have to remove the calls to the Close() method, that's going to cause real deadlock because the port can't be closed if the DataReceived call is still stuck in the ReadLine() call. Do use BeginInvoke() instead of Invoke(), less danger of deadlock that way.

Upvotes: 5

Related Questions