GUI lag in VB.NET

1.8k Views Asked by At

I have a program for carrying out serial communication. After sending a command, the program has to wait for a short while to receive the data. The data is received using ActiveX MsComm

http://msdn.microsoft.com/en-us/library/aa259393%28v=vs.60%29.aspx

The psuedocode looks something like this:

timer_tick

send data (node 1)

sleep(500) 'receives data for node 1 meanwhile

send data (node 2)

. . .

the problem is that sleep causes the GUI to lag. I've thought of a few alternatives

1) using an artificial counter in the timer to approximate 500ms before sending the next command but does not seem like a very good idea to me 2) using a thread instead of a timer? and setting it as a background thread

http://msdn.microsoft.com/en-us/library/system.threading.thread.isbackground(v=vs.71).aspx

3) making use of application.doevents?

I am not so familiar with option 2 or 3, therefore i am hesitant to introduce possibly complicating elements into my code. Can anybody advise of any alternative that's better suited for this problem or to start working towards option 2 or 3?

2

There are 2 best solutions below

3
On BEST ANSWER

The issue is the fact that your timer is running on the UI thread, so when you sleep in that sub-routine, you are sleeping the UI thread (hence the 'lag').

The best probably easiest to understand option is to add a BackgroundWorker control to your form and move your sending (and sleep) code into the worker DoWork routine, which will not block the UI thread.

Something like this:

Worker_DoWork
    send data (node 1)
    sleep(500) 'receives data for node 1 meanwhile
    send data (node 2)
End Sub

The use of DoEvents is strongly discouraged - there are very few reasons for using this in .NET

2
On

The problem occurs because you are putting the UI thread to sleep. In .NET 4.5, you can use Task.Delay to make your code execute only after a certain interval without blocking any thread, eg:

Dim port As New SerialPort
...
Public Async Sub SomeButton_Click()
    port.WriteLine("First")
    Await Task.Delay(500)
    port.WriteLine("Second")
    Await Task.Delay(300)
    port.WriteLine("Third")
End Sub

In this case the first WriteLine executes on the UI thread, a wait occurs in the background for 500ms (actually using a timer), then execution resumes on the original thread (the UI thread) and the second WriteLine occurs in the UI thread as well. Another 300 ms wait then the third WriteLine occurs in the UI thread as well.

The Async\Await combination allows you to write such code in a very clean manner, without involving other components like BackgroundWorker. It also allows you to do things that Background Worker just can't do, like execute multiple asynchronous calls. In such a case you would need to use multiple workers where now you just write one more statement

Note that the Async Sub syntax is used only for event handlers. In all other cases you should use Async Function SomeFunction() As Task.

If you want to run the entire sequence in a background thread, you can use Task.Run.

Public Async Sub SomeButton_Click()
    Task.Run(Async Function () 
            Await SendAndWait
        End Function)
End Sub

public Async Function SendAndWait As Task
    _port.WriteLine("First")
    Await Task.Delay(500)
    _port.WriteLine("Second")
    Await Task.Delay(300)
    _port.WriteLine("Third")
End Function

The real beauty of using Tasks, is that you can combine multiple asynchronous operations, something that's nearly impossible with BackgroundWorker. In this case, you can read data from a file or database asynchronously and send it to the serial port asynchronously, with very simple code:

public Async Function SendAndWait(filePath As String) As Task
    Dim line As String
      Using reader As New StreamReader(filePath)            
        For i=0 To 3 
            line=Await reader.ReadLineAsync() 
            _port.WriteLine(line)
            Await Task.Delay(500)                
        Next 
    End Using 
End Function

You can use the async/await keywords in .NET 4.0 code as well by adding the Microsoft.Bcl.Async package to your project.