0

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?

Matt Wilko
  • 26,994
  • 10
  • 93
  • 143
KRC
  • 328
  • 2
  • 16
  • 1
    Why not simply do this in a worker thread? – L-Four Nov 26 '14 at 08:48
  • Ok, i'm on it! Thanks! – KRC Nov 26 '14 at 09:19
  • 2
    Why are you using the COM component instead of .NET's [SerialPort](http://msdn.microsoft.com/en-us/library/system.io.ports.serialport%28v=vs.110%29.aspx) ? Also, are you waiting to ensure you receive a response? Would it be OK to send the second command earlier if a response was received before 500ms? – Panagiotis Kanavos Nov 26 '14 at 09:35
  • Using .NET framework 1.1 ;) – KRC Nov 27 '14 at 00:15

2 Answers2

3

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

Matt Wilko
  • 26,994
  • 10
  • 93
  • 143
  • No reason to use a BackgroundWorker or Thread.Sleep either, when you can use Tasks and Task.Delay, avoiding blocking entirely. – Panagiotis Kanavos Nov 26 '14 at 09:34
  • Not what you think. It doesn't put anything to sleep. It starts a background timer, releases the original thread and picks up once an timer fires. It doesn't use nor put any thread to sleep. – Panagiotis Kanavos Nov 26 '14 at 09:42
  • Moreover, using Tasks with `Async/Await` is easier and simpler than BackgroundWorker in all cases. [Stephen Cleary](http://blog.stephencleary.com/2013/05/taskrun-vs-backgroundworker-intro.html) wrote an exhaustive series of posts displaying all alternatives to what BackgroundWorker does and the cases that it just can't cover, like reporting progress message. – Panagiotis Kanavos Nov 26 '14 at 10:04
2

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.

Panagiotis Kanavos
  • 120,703
  • 13
  • 188
  • 236
  • 1
    I am not using .NET 4.5, so i can only use BackgroundWorker. But thanks for alerting me to another alternative should i find myself working on the latest version of .NET someday :) – KRC Nov 27 '14 at 00:15
  • You can use all that in 4.0 as well. Task were added in .NET 4.0 while the [Microsoft.Bcl.Async](https://www.nuget.org/packages/Microsoft.Bcl.Async) package adds support for `async/await` to 4.0 for Visual Studio 2012+. Even if you use VS 2010, you can upgrade to the free Visual Studio 2013 Pro (sorry "Community") edition – Panagiotis Kanavos Nov 27 '14 at 08:18