vb.net - how can I delay processing within a loop?

vb.net - how can I delay processing within a loop?

Author
Discussion

TonyRPH

Original Poster:

13,303 posts

182 months

Friday 9th February 2018
quotequote all
My app sends a specific set of frequencies (derived from an array) to an Arduino controlled function generator.

This same app reads data from a USB oscilloscope (see flowchart below).

Now, my oscilloscope is slow to 'settle' at frequencies below 20Hz - so I need to introduce a delay (in the read process) for frequencies below 20Hz.

There is a (very) simplified version of my code below - I think the delay needs to be introduced inside the "Case 5 to 50" statement.

I have tried additional timers, and also "Thread.Sleep()", but thread.sleep just pauses the processing but the Timer continues in the background, so when the 'sleep wakes up' I get a sudden burst of activity (not good!).

I've also tried stopping the timer and restarting it within the loop.

Maybe my entire approach is just wrong...

TIA for any help.

For testing, this code will run as is with a form, button, 4 labels and a rich text box.

In "Dim freqList() As Decimal" below I have had to place brackets, to get around the PH brace "{" issue...


Imports System.Threading

Public Class Form1
Dim timeLoop As Integer = 0
Public timeBase As String = Nothing
Public freq As Decimal
Dim freqList() As Decimal = (10, 15, 20, 25, 30, 50, 100, 500, 1000, 5000, 10000, 50000, 100000) ' <--- change brackets for braces!!!

Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load

End Sub

Private Sub readScope()
Me.RichTextBox1.AppendText(freq)
Me.RichTextBox1.AppendText(Environment.NewLine)
End Sub

Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick
' timer 1 delays the loop count

If timeLoop <= 12 Then
freq = freqList(timeLoop)
freq = Math.Round(freq, 2)
Label1.Text = timeLoop
'Label1.Text = freq
'freq = freqs(x)
End If

If timeLoop >= 12 Then
Timer1.Enabled = False
Label1.Text = timeLoop
End If

Select Case freq
Case 5 To 50
If Not timeBase = "3" Then
timeBase = "3"
Label2.Text = timeBase
End If
'SerialPort1.WriteLine("f" & " " & freq) 'fast setting
If freq <= 11 Then
' 'Call readDelay()
Thread.Sleep(6000)
End If
Call readScope()

Case 50 To 500
If Not timeBase = "5" Then
timeBase = "5"
Label2.Text = timeBase
End If
'SerialPort1.WriteLine("f" & " " & freq) 'fast setting
Call readScope()

Case 500 To 5000
If Not timeBase = "8" Then
timeBase = "8"
Label2.Text = timeBase
End If
'SerialPort1.WriteLine("f" & " " & freq)
Call readScope()

Case 5000 To 50000
If Not timeBase = "11" Then
timeBase = "11"
Label2.Text = timeBase
End If
'SerialPort1.WriteLine("f" & " " & freq)
Call readScope()

Case 50000 To 1100000
If Not timeBase = "15" Then
timeBase = "15"
Label2.Text = timeBase
End If
'SerialPort1.WriteLine("f" & " " & freq)
Call readScope()
End Select
timeLoop = timeLoop + 1
End Sub

Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
RichTextBox1.Clear()
freq = freqList(0)
'SerialPort1.WriteLine("f" & " " & freq)
Timer1.Enabled = True
Timer1.Interval = 3000
End Sub
End Class




Edited by TonyRPH on Friday 9th February 10:34

anonymous-user

68 months

Friday 9th February 2018
quotequote all
I haven't coded for 20 years so can't answer your question directly (sorry for wasting your time) but back in the 90s I was writing code for the Stewart F1 team and had to introduce a delay as my code was too quick for the radio data transmissions. Instead of an official delay or timer I actually made my loop do an integral count so I could tweak the delay just by changing the integer.

Worked perfectly but probably would upset a lot of purists

nyt

1,889 posts

164 months

Friday 9th February 2018
quotequote all
I think that you need: System.Threading.Thread.Sleep(number of milliseconds).


SystemParanoia

14,343 posts

212 months

Friday 9th February 2018
quotequote all
Doesn't .NET have "promises" or "callbacks" or the ability to process functions asyncronously?

TonyRPH

Original Poster:

13,303 posts

182 months

Friday 9th February 2018
quotequote all
keirik said:
Stuff.
I believe I already tried this! Great minds think alike! :P

nyt said:
I think that you need: System.Threading.Thread.Sleep(number of milliseconds).
This yields the same behaviour as Thread.Sleep()

SystemParanoia said:
Doesn't .NET have "promises" or "callbacks" or the ability to process functions asyncronously?
I am a relative novice at .net - while I've seen reference to 'async' and 'await' during my searches for a solution, I didn't understand their workings.

Async processing (if it is what I understand it to be) might well be the answer here though - I shall have to research it a bit more...

I have an idea that callbacks and promises might be c# / c++ only.




zippy3x

1,347 posts

281 months

Friday 9th February 2018
quotequote all
How long does your oscilloscope take to settle at the lower frequencies?

TonyRPH

Original Poster:

13,303 posts

182 months

Friday 9th February 2018
quotequote all
zippy3x said:
How long does your oscilloscope take to settle at the lower frequencies?
Roughly about 0.5 to 1 second - it's quite difficult to time it.




zippy3x

1,347 posts

281 months

Friday 9th February 2018
quotequote all
TonyRPH said:
Roughly about 0.5 to 1 second - it's quite difficult to time it.
Ok, then you're problem would seem to be the fact your waiting 6 seconds in a 3 second loop.

would a Thread.Sleep(1500) not work?

(incidentally if you add the async keyword to you method declaration you could use "await Task.Delay(1500);" for async version of Thread.Sleep)

TonyRPH

Original Poster:

13,303 posts

182 months

Friday 9th February 2018
quotequote all
zippy3x said:
Ok, then you're problem would seem to be the fact your waiting 6 seconds in a 3 second loop.
According to the timings when I run the code above, the rich text box is populated every 3 seconds?

zippy3x said:
would a Thread.Sleep(1500) not work?
No - this causes the loop to suddenly catch up when the sleep 'wakes up' and suddenly sends a flood of queued data.

zippy3x said:
(incidentally if you add the async keyword to you method declaration you could use "await Task.Delay(1500);" for async version of Thread.Sleep)
It's the async version of thread sleep that I need, as the Timer1 loop needs to be paused while the waiting to read the data.

Which method declaration would I need to add this to, and how do I add it specifically?

Remember you're talking to a relative novice here! :P

Thanks.

nyt

1,889 posts

164 months

Friday 9th February 2018
quotequote all
How about an outer loop around a smaller thread.sleep to get the delay that you need - and add a doevents for good measure.

//for a 3 second delay

For i = 1 to 30

System.Threading.Thread.Sleep(100)
System.Windows.Forms.Application.DoEvents()

Next

You could go even finer on the Sleep - say System.Threading.Thread.Sleep(10) and increase the outer delay


TonyRPH

Original Poster:

13,303 posts

182 months

Friday 9th February 2018
quotequote all
nyt said:
How about an outer loop around a smaller thread.sleep to get the delay that you need - and add a doevents for good measure.

<snip>
I tried this, but it didn't work either.



zippy3x

1,347 posts

281 months

Friday 9th February 2018
quotequote all
TonyRPH said:
It's the async version of thread sleep that I need, as the Timer1 loop needs to be paused while the waiting to read the data.

Which method declaration would I need to add this to, and how do I add it specifically?

Remember you're talking to a relative novice here! :P

Thanks.
Private Async Sub StartButton_Click(sender As Object, e As RoutedEventArgs)

' Call and await separately.
'Task<int> getLengthTask = AccessTheWebAsync();
'' You can do independent work here.
'int contentLength = await getLengthTask;

Dim contentLength As Integer = Await AccessTheWebAsync()

ResultsTextBox.Text &=
String.Format(vbCrLf & "Length of the downloaded string: {0}." & vbCrLf, contentLength)
End Sub

from here : https://docs.microsoft.com/en-us/dotnet/visual-bas...

or in your case Await Task.Delay(whatever)

TonyRPH

Original Poster:

13,303 posts

182 months

Friday 9th February 2018
quotequote all
After some experimentation, I realised that Await / Task won't work either.

The problem is, as soon as I attempt to read the scope, the data is returned without delay (whether it's correct or not!).

So task await has nothing to wait for...

And I have no way of knowing if the scope has 'caught up' as there is no indication - I have a "Data Ready*" option in the DLL for the scope, however that returns "Yes" as soon as the scope goes into run mode, and not whether it has triggered etc.

I think I need to go back to the drawing board here.

It's a tricky one this!

From the manual:

  • "DataReady Indicates if there is fresh data available"
Well, as soon as you hit "Run" there is fresh data available lol.


nyt

1,889 posts

164 months

Friday 9th February 2018
quotequote all
Can't you just throw away all data that is waiting when your 'sleep' is finished and carry on with good data?

TonyRPH

Original Poster:

13,303 posts

182 months

Friday 9th February 2018
quotequote all
nyt said:
Can't you just throw away all data that is waiting when your 'sleep' is finished and carry on with good data?
No, the process works like this:

loop and generate frequency from array
send frequency information to arduino, which generates a sinewave
sinewave then sent to device under test
scope reads output of device under test
the scope info is then parsed by my app

So it all needs to be real time - so I do need to stop sending / receiving data while waiting for the scope to catch up.

I might just setup another timer, and place the initial loop in the first (longer) timer - and then use the existing timer to resume processing.

I can't think of any other way to do it... (apart from manual steps!!)

I just need to step through the process slowly...

  • Or try and do it in C / C++ lol. (no chance)

zippy3x

1,347 posts

281 months

Friday 9th February 2018
quotequote all
So, first things first. Regarding the oscilloscope,

You send a string to it telling it what frequency you want to read?

You get a (probably erroneous) message saying data ready?

But it's not? or its is except the lower frequency?

If it is the lower frequency you need to wait for some duration and then read again presuming this time the data is correct?

is that about right?

ReverendCounter

6,087 posts

190 months

Friday 9th February 2018
quotequote all
In case it quickly doesn't become obvious, I have no idea what I'm talking about.

If you know what your processor speed is, can you integrate a large calculation as a side routine, where the calculation takes a specific time to carry out which is equivalent to the delay required.

Then once the calc is done, and the specific and only result is generated, that answer is used to trigger the rest of the code operation.

Candidate for Most Bizarre Workaround 2018?

nyt

1,889 posts

164 months

Friday 9th February 2018
quotequote all
TonyRPH said:
nyt said:
Can't you just throw away all data that is waiting when your 'sleep' is finished and carry on with good data?
No, the process works like this:

loop and generate frequency from array
send frequency information to arduino, which generates a sinewave
sinewave then sent to device under test
scope reads output of device under test
the scope info is then parsed by my app

So it all needs to be real time - so I do need to stop sending / receiving data while waiting for the scope to catch up.

I might just setup another timer, and place the initial loop in the first (longer) timer - and then use the existing timer to resume processing.

I can't think of any other way to do it... (apart from manual steps!!)

I just need to step through the process slowly...

  • Or try and do it in C / C++ lol. (no chance)
Sorry, I'm not following.
If you throw away the (presumably incorrect) data that you've received from the oscilloscope while the delay was active, and then prices the presumably correct data then your app will still be realtime?

Pseudo code:

'SerialPort1.WriteLine("f" & " " & freq) 'fast setting
If freq <= 11 Then
Thread.Sleep(6000)
End If
'Now read data until you get a 'data not ready' from the 'scope
Call ReadAndDiscardData()
'Now read the good data
Call readScope()

If you never get a 'data not ready' then read as many points as you expect to receive from the 'scope in the delay period.


TonyRPH

Original Poster:

13,303 posts

182 months

Friday 9th February 2018
quotequote all
zippy3x said:
So, first things first. Regarding the oscilloscope,

You send a string to it telling it what frequency you want to read?
Yes, I send a read command as defined by the DLL provided with the scope.

zippy3x said:
You get a (probably erroneous) message saying data ready?
Yes, but it's not erroneous as such, just poorly defined by the scope DLL / scope itself

zippy3x said:
But it's not? or its is except the lower frequency?
The scope receives the low frequency, but requires a finite 'settling time' to provide an accurate reading (0.5s to 1s at frequencies < 20..40Hz).

zippy3x said:
If it is the lower frequency you need to wait for some duration and then read again presuming this time the data is correct?
This is correct.

zippy3x said:
is that about right?
Yep. - and thanks for taking the time to respond.

ReverendCounter said:
Same suggestion as keirik
See 2nd post above, from keirik. This doesn't quite work for me...

buggalugs

9,257 posts

251 months

Friday 9th February 2018
quotequote all
Keep reading the scope in a loop every 100ms until you get 3 readings the same?