Multithread operations

M

Marco Trapanese

Hello,

in my application I need to send on a serial port a request, wait for
the answer and update some controls on the form. In the meanwhile the
user should be able to use the GUI without any problem.

So I ended up in the following way:

* in the tick event of a timer (100 ms) I create a new thread. It sends
the request, receives the answer and updates the controls (through
Invoke method, of course).


It works fine, but there are no difference from do the same in the main
thread. I'm talking about some delays when clicking buttons. These
delays are due to the send/receive/elaborate functions. In fact, if I
comment out them I don't experience delays anymore.

I bet the multithread solution should fix the issue, but I was wrong.

May you help me? How would you do this?

Thanks
Marco
 
T

Tom Shelton

Hello,

in my application I need to send on a serial port a request, wait for
the answer and update some controls on the form. In the meanwhile the
user should be able to use the GUI without any problem.

So I ended up in the following way:

* in the tick event of a timer (100 ms) I create a new thread. It sends
the request, receives the answer and updates the controls (through
Invoke method, of course).


It works fine, but there are no difference from do the same in the main
thread. I'm talking about some delays when clicking buttons. These
delays are due to the send/receive/elaborate functions. In fact, if I
comment out them I don't experience delays anymore.

I bet the multithread solution should fix the issue, but I was wrong.

May you help me? How would you do this?

Thanks
Marco


If you using the standard Winform timer - System.Windows.Forms.Timer, then you
aren't multithreaded. The timer tick happens on the the same thread as the
form, so any long running operations you perform will block the UI.

I haven't worked with a serial port in .NET, but looking I don't see any async
methods. So, if you need to poll, then either use a real threaded timer, such
as System.Threading.Timer or System.Timers.Timer. But, remember - anytime you
do something on the background thread, you can not directly interact with the
UI. Any notifications that the UI should act on, need to be marshalled to the
main forms thread - simply because windows forms controls are not threadsafe.

Here is a link to part 1 of a 3 part series on the topic of multithreading
with windows forms:

http://msdn.microsoft.com/en-us/library/ms951089.aspx

HTH
 
M

Marco Trapanese

Tom Shelton ha scritto:
If you using the standard Winform timer - System.Windows.Forms.Timer, then you
aren't multithreaded. The timer tick happens on the the same thread as the
form, so any long running operations you perform will block the UI.


uh... may be I miser understood something.
The tick event contains the following:

Private Sub Timer_Tick(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles Timer.Tick
Dim trd As New Thread(AddressOf UpdateAll)
trd.IsBackground = True
trd.Start()
End Sub

I was pretty sure I create a new thread, so the main thread is free to
do other things while the trd is running. Am I wrong?

So, if you need to poll, then either use a real threaded timer, such
as System.Threading.Timer or System.Timers.Timer.


I'll look at them.

But, remember - anytime you
do something on the background thread, you can not directly interact with the
UI. Any notifications that the UI should act on, need to be marshalled to the
main forms thread - simply because windows forms controls are not threadsafe.


To solve this I write:

If Me.InvokeRequired Then
Me.Invoke(New MethodInvoker(AddressOf UpdateAll))
Else
...
End If

In the sub which updates the controls.

Here is a link to part 1 of a 3 part series on the topic of multithreading
with windows forms:

http://msdn.microsoft.com/en-us/library/ms951089.aspx


Thanks, I'm reading...

Marco
 
T

Tom Shelton

Tom Shelton ha scritto:



uh... may be I miser understood something.
The tick event contains the following:

Private Sub Timer_Tick(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles Timer.Tick
Dim trd As New Thread(AddressOf UpdateAll)
trd.IsBackground = True
trd.Start()
End Sub

I was pretty sure I create a new thread, so the main thread is free to
do other things while the trd is running. Am I wrong?

Ok... That's ok. Your explicitly creating a thread in the timer event. It
sounded like you were performing the work in the Tick event - which is not on
a different thread.

So, what are you doing on the worker thread? It should not be blocking the
UI. Can you post a small simple example that demonstrates you problem?
 
F

Family Tree Mike

Marco Trapanese said:
Tom Shelton ha scritto:



uh... may be I miser understood something.
The tick event contains the following:

Private Sub Timer_Tick(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles Timer.Tick
Dim trd As New Thread(AddressOf UpdateAll)
trd.IsBackground = True
trd.Start()
End Sub

I was pretty sure I create a new thread, so the main thread is free to do
other things while the trd is running. Am I wrong?




I'll look at them.




To solve this I write:

If Me.InvokeRequired Then
Me.Invoke(New MethodInvoker(AddressOf UpdateAll))
Else
...
End If

In the sub which updates the controls.




Thanks, I'm reading...

Marco


So ten times per second you are sending a request to a port, waiting for a
response, and updating the UI? From the original post that is what it
souinds like you are doing in UpdateAll. Depending on how long the response
over the port takes, this sounds ambitious.
 
C

Cor Ligthert[MVP]

Marco,

Often this kind of problems are solved by simple disabling the timer at the
beginning of the tick event and enable it again at the end.

Cor
 
M

Marco Trapanese

Tom Shelton ha scritto:
So, what are you doing on the worker thread? It should not be blocking the
UI.


The sub called is this:

If Me.InvokeRequired Then
Me.Invoke(New MethodInvoker(AddressOf UpdateAll))
Else
Dim o(7) As Boolean
Dim i(7) As Boolean

optLed.Checked = True
For j As Integer = 0 To 7
o(j) = IOItemArray.Item(j).Value
Next
WriteOutputs(o)
ReadInputs(i)
For j As Integer = 0 To 7
IOItemArray.Item(j + 8).Value = i(j)
Next
optLed.Checked = False
End If

Where

IOItemArray is a List of user control. Setting the value property will
change the text in their own label.

WriteOutput is a short sub (ReadInputs calls ReadCoils)

Try
Dim slaveID As Byte = 1
Dim startAddress As UShort = 16
master.WriteMultipleCoils(slaveID, startAddress, Values)
Catch ex As Exception
MessageBox.Show(ex.Message)
End Try


Both uses the http://nmodbus.com/ dll to communicate with the external
object trough the Modbus protocol.


As you can see it's a very simple code.
I improved a lot the performance removing the instruction to open and
close the serial port each time. In this way the delays are more
acceptable, but still present (in fact, I'm actually reducing the time
spent in the sub) so the new thread still block the GUI.

Can you post a small simple example that demonstrates you problem?


I can't. To reproduce the problem you need the external peripheral I bet.

Thanks
Marco
 
M

Marco Trapanese

Family Tree Mike ha scritto:
So ten times per second you are sending a request to a port, waiting for
a response, and updating the UI? From the original post that is what it
souinds like you are doing in UpdateAll. Depending on how long the
response over the port takes, this sounds ambitious.


I think it isn't ambitious at all. You can find the code of the
UpdateAll sub in the message I've just sent few minutes ago.

The time for a complete communication over the serial ports is about
tens of ms. So I have 50 ms minimum to update few labels. I hope my 2
GHz proc is able to do the dirty job :)

Anyway, you pointed out a real issue. The next step will be to run the
code on an embedded board under Windows XP Embedded. If there will be
problems of this type I'm afraid to use Linux and QT for the graphical
interface.

Marco
 
M

Marco Trapanese

Cor Ligthert[MVP] ha scritto:
Often this kind of problems are solved by simple disabling the timer at
the beginning of the tick event and enable it again at the end.


Thank you for you answer. This is the first thing I tried, I forgot to
mention before, sorry.

Anyway, it didn't do the trick

Marco
 
T

Tom Shelton

Tom Shelton ha scritto:



The sub called is this:

If Me.InvokeRequired Then
Me.Invoke(New MethodInvoker(AddressOf UpdateAll))
Else
Dim o(7) As Boolean
Dim i(7) As Boolean

optLed.Checked = True
For j As Integer = 0 To 7
o(j) = IOItemArray.Item(j).Value
Next
WriteOutputs(o)
ReadInputs(i)
For j As Integer = 0 To 7
IOItemArray.Item(j + 8).Value = i(j)
Next
optLed.Checked = False
End If

Marco,

You are spawning UpdateAll on it's own thread with the call to Thread.Start,
yet UpdateAll is imediately using Invoke to marshal back to the UI thread.
Basically, this means that UpdateAll is always running on the UI thread,
you might as well just be calling UpdateAll directly from the timer - it
would probably actually improve the performance, because you wouldn't have
the marshalling overhead :)

Personally, now that I'm looking at this, I would probably just use a
BackgroundWorker component. And structure the code some thing like:

private o(7) As Boolean ' move these to the class level
private i(7) as boolean

Timer_Tick
timer.Disabled = true
optLed.Checked = True
For J As Integer = 0 to 7
o(j) = IOItemArray.Item(j).Value
Next
myBackgroundWorker.RunWorkerAsync()

myBackgroundWorker_DoWork
WriteOutputs(o)
ReadInputs(i)

myBackgroundWoker_RunWorkCompleted
For j As Integer = 0 To 7
IOItemArray.Item(j+8).Value = i(j)
Next
optLed.Checked = false
time.Enabled = true


This way, the stuff that is interacting with the ui, happens on the UI thread,
and the part that will most likely block (the WriteOutputs and ReadInputs)
will be on their own thread.
 
M

Marco Trapanese

Tom Shelton ha scritto:
You are spawning UpdateAll on it's own thread with the call to Thread.Start,
yet UpdateAll is imediately using Invoke to marshal back to the UI thread.


Oh dear!

Personally, now that I'm looking at this, I would probably just use a
BackgroundWorker component. And structure the code some thing like:

[cut]


This way, the stuff that is interacting with the ui, happens on the UI thread,
and the part that will most likely block (the WriteOutputs and ReadInputs)
will be on their own thread.


Interesting. Tomorrow I'll try it and then I'll write here a feedback.

Thank you very much
Marco
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top