Progress Reporting causes BackGroundWorker to Run Slow. VB - vb.net

I am using a BackgroundWorker to write to pixels on an internal bitmap, using a loop.
The BackgroundWorker DoWork routine does not access any GUI components.
If I run the loop without any Progress Reporting, the loop takes about 2 seconds to complete, which is satisfactory.
If I add Progress Reporting, the loop takes about 20 seconds to complete!! Not good!!
I notice that when Progress Reporting is on and the loop is running, the form cannot be dragged about the screen, and my Cancel button (code for this not included here) is not responded to until the loop completes.
Its as if the program was not running in a BackGroundWorker.
The Progress Reporting code is quite standard, and has been used a number of times before:
Dim x As Integer = 5000
Dim y As Integer = 2500
Dim i as Integer = 0
For a As Integer = 0 To x
For b As Integer = 0 To y
' Other fast code
worker.ReportProgress(CInt(100 * (i / (x * y)))
i += 1
Next
Next
Private Sub BGW_ProgressChanged(sender As Object, e As System.ComponentModel.ProgressChangedEventArgs) Handles BGW.ProgressChanged
ProgressBar.Value = e.ProgressPercentage
StatusLabel.Text = "Progress: " & e.ProgressPercentage & "%"
End Sub
This all seems good and standard, until I realised that the ReportProgress line is actually sending its value to the ProgressChanged routine 12,500,000 times, when actually we only need to send a value 100 times. It was the flood of values that caused the problem.
This code works perfectly. I hope it is useful to someone else:
Dim x As Integer = 5000
Dim y As Integer = 2500
Dim i as Integer = 0
Dim j As Double = (x * y) / 100
Dim k As Integer = 0
For a As Integer = 0 To x
For b As Integer = 0 To y
' Other fast code
i += 1
If i > j Then
k += 1
worker.ReportProgress(CInt(k))
i = 0
End If
Next
Next

You should use a public variable (Like "ReportValue") and a timer .
The timer is in a continuos loop and it set the value of the progressBar using the "ReportValue" .
On the other side , your backgroundWorker reports the progress in the "ReportValue" variable .
So you can refresh your ProgressBar without losing time in handling the [Progress state change]
then add this code to the timer:
If Not ReportValue = MyProgressBar.Value Then
MyProgressBar.Value = ReportValue
end if
The timer will refresh the progressbar value only if the value is changed
( preventing an high CPU usage )
Hope it helps :)

An easy way is to only report progress if there is progress to report:
Dim x As Integer = 5000
Dim y As Integer = 2500
Dim i as Integer = 0
Dim oldProgress = 0
For a As Integer = 0 To x
For b As Integer = 0 To y
' Other fast code
Dim progress = CInt(100 * (i / (x * y))
If progress > oldProgress Then
worker.ReportProgress(progress)
oldProgress = progress
End If
i += 1
Next
Next

Related

Visual Basic Threading with More than 2 Threads

I am currently trying to produce a sound using a custom beep class. One of the methods of the beep class is to produce multiple octaves of a sound with a given frequency, so one sound is the frequency, another is the frequency * 2, another is the frequency * 4, etc.
I am attempting to use threading to make these all sound together by giving each sound its own thread. However, I notice that it still plays sounds once-at-a-time. I can confirm that the sounds are not interrupting the flow of the program itself, however, so the threading is working in that capacity.
Here is the code I am using. The idea is that for NumOctave times, a new frequency is generated based off the first (and amplitude), and is set to sound in its own thread. However, it appears that the threads are queuing, rather than truly executing asynchronously from each other. What would be the best way to get the intended behavior?
Shared Sub OctBeep(ByVal Amplitude As Integer,
ByVal Frequency As Integer, ByVal NumOctaves As Integer,
ByVal Duration As Integer, ByVal NewThread As Boolean)
Dim threads As List(Of Thread) = New List(Of Thread)
Dim powTwo As Integer = 1
Dim powTen As Integer = 1
For oct As Integer = 1 To NumOctaves
Dim thisOct As Integer = oct
Dim thisThread As New Thread(
Sub()
Dim newFreq, newAmp As Integer
newFreq = Frequency * powTwo
newAmp = Amplitude / powTen
BeepHelp(newAmp, newFreq, Duration)
End Sub
)
thisThread.IsBackground = True
thisThread.Start()
powTwo *= 2
powTen *= 10
Next
End Sub
Here is BeepHelp()
Shared Sub BeepHelp(ByVal Amplitude As Integer,
ByVal Frequency As Integer,
ByVal Duration As Integer)
Dim A As Double = ((Amplitude * 2 ^ 15) / 1000) - 1
Dim DeltaFT As Double = 2 * Math.PI * Frequency / 44100
Dim Samples As Integer = 441 * Duration \ 10
Dim Bytes As Integer = Samples * 4
Dim Hdr() As Integer = {&H46464952, 36 + Bytes, &H45564157,
&H20746D66, 16, &H20001, 44100,
176400, &H100004, &H61746164, Bytes}
Using MS As New MemoryStream(44 + Bytes)
Using BW As New BinaryWriter(MS)
For I As Integer = 0 To Hdr.Length - 1
BW.Write(Hdr(I))
Next
For T As Integer = 0 To Samples - 1
Dim Sample As Short = CShort(A * Math.Sin(DeltaFT * T))
BW.Write(Sample)
BW.Write(Sample)
Next
BW.Flush()
MS.Seek(0, SeekOrigin.Begin)
Using SP As New SoundPlayer(MS)
SP.PlaySync()
End Using
End Using
End Using
End Sub
End Class
I ran into this same issue before writing a speech synthesizer class. There is a difference between Multi-threading(Just moves to a different thread so main program still works) & Parallel Processing(Moves the process to its own processor). You want to look into Parallel Processing instead

Waiting for multiple Backgroundworkers to complete

I have to work with multiple big 2-dimensional arrays (1024 x 128 for example) and in a section of my code I need to transpose some (up to 12 of them).
The procedure takes a fair amount of time and I'm trying to speed it up as much as possible. Knowing that VB.NET supports multi-threading I gave a read here and there to different sources and could come up with the following code in the main subroutine:
RunXTransposingThreads(Arr1, Arr2, Arr3, ...)
Using BackgroundWorkers as a part of my solution:
Private Sub RunXTransposingThreads(ParamArray ArraysToTranspose() As Array)
Dim x = CInt(ArraysToTranspose.GetLength(0)) - 1
Dim i As Integer
For i = 0 To x
Dim worker As New System.ComponentModel.BackgroundWorker
AddHandler worker.DoWork, AddressOf RunOneThread
AddHandler worker.RunWorkerCompleted, AddressOf HandleThreadCompletion
worker.RunWorkerAsync(ArraysToTranspose(i))
Next
End Sub
Private Sub RunOneThread(ByVal sender As System.Object, ByVal e As System.ComponentModel.DoWorkEventArgs)
Dim Transposed(,) As Single = Array.CreateInstance(GetType(Single), 0, 0) ' I need this to be like that in order to use other functions later
Transposed = Transpose2dArray(CType(e.Argument, Single(,)))
e.Result = Transposed
End Sub
Private Sub HandleThreadCompletion(ByVal sender As Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs)
Debug.Print("Process complete")
End Sub
Function Transpose2dArray(Of Array)(ByVal inArray As Array(,)) As Array(,)
Dim x = CInt(inArray.GetLength(1))
Dim y = CInt(inArray.GetLength(0))
Dim outArray(x - 1, y - 1) As Array
For i = 0 To x - 1
For j = 0 To y - 1
outArray(i, j) = inArray(j, i)
Next
Next
Transpose2dArray = outArray
End Function
The threads seem to work, because at some point after the execution of RunXTransposingThreads, I see on my screen a number of "Process complete". The question is: how do I stop the code in main from being executed if I don't have yet transposed arrays?
As others have said, BackgroundWorker is obsolete. Fortunately there are many other modern ways of doing this.
Therefore I won't show you something that makes your code works with BackgroundWorker. Instead I'll show you how to do the same thing by using Tasks, one of those modern ways. Hope it helps.
Function RunXTransposingTasks(ParamArray ArraysToTranspose() As Array) As Array
Dim taskList = New List(Of Task(Of Single(,))) ' our tasks returns Arrays
For Each arr In ArraysToTranspose
Dim r = arr
taskList.Add(Task.Run(Function() Transpose2dArray(r)))
Next
Task.WhenAll(taskList) ' wait for all tasks to complete.
Return taskList.Select(Function(t) t.Result).ToArray()
End Function
Function Transpose2dArray(inArray As Array) As Single(,)
Dim x = inArray.GetLength(1) - 1
Dim y = inArray.GetLength(0) - 1
Dim outArray(x, y) As Single
For i = 0 To x
For j = 0 To y
outArray(i, j) = inArray(j, i)
Next
Next
Return outArray
End Function
' Usage
' Dim result = RunXTransposingTasks(Arr1, Arr2, Arr3, ...)
You could try using build-in function to copy data
Array.Copy(inArray, outArray, CInt(inArray.GetLength(1)) * CInt(inArray.GetLength(0)))
There's also some great example on how to use Parallel.ForEach.

how to decrease CPU usage when using multiple threads VB.net?

I am using vb.net , .NetFramework 2.0 .
My application gets live stock prices from google and updates stocks in database each 10 seconds.
I get this code for multithreading. When application starts updating stocks in database(about 200 stocks) , the update takes up to 3 seconds but it increase CPU usage from 10 % to 70 % or 80 %.
What is the best way to to update database without getting CPU increase to high level?
I observed that all threads works at the same time. how to make each thread wait until the second ends?
This is my code. The problem is in function updateThreaded2().
Please I need quick help. Thanks
Public Function Update2(ByVal l As SortableBindingList(Of NewStockList)) As Long
res = 0
UThread = New System.Threading.Thread(AddressOf UpdateThreaded2)
If Me.URunning = True Then
Else
Try
Me.URunning = True
Interlocked.Exchange(Me.UCount, 0) 'threadsafe method of assigning static value
Interlocked.Exchange(Me.UDone, 0) 'threadsafe method of assigning static value
UThread.Priority = Threading.ThreadPriority.BelowNormal
UThread.IsBackground = True
UThread.Start(l)
Return 0
Catch ex As Exception
End Try
End If
End Function
Private Sub UpdateThreaded2(ByVal l As SortableBindingList(Of NewStockList))
Dim i As Integer = 0
Dim threadcount As Integer = Math.Min(Me.MaxThreads, Me.Stocks.Count)
Dim threads(threadcount - 1) As SUTC
Try
While i < Me.Stocks.Count
For j As Integer = 0 To threadcount - 1
If threads(j) Is Nothing Then
If i < Me.Stocks.Count Then
threads(j) = New SUTC(Me.Stocks(i), Me.DefaultService, AdjustSplits, Use20Minutes, l)
threads(j).Thread.Priority = Threading.ThreadPriority.BelowNormal
threads(j).Thread.IsBackground = True
threads(j).Thread.Start()
i += 1
End If
ElseIf threads(j).UpdateState = 0 Then
If i < Me.Stocks.Count Then
SecUpd(j) = Me.Stocks(i).symbol
threads(j) = New SUTC(Me.Stocks(i), Me.DefaultService, AdjustSplits, Use20Minutes, l)
threads(j).Thread.Priority = Threading.ThreadPriority.BelowNormal
threads(j).Thread.IsBackground = True
threads(j).Thread.Start()
i += 1
End If
End If
Next
Dim running As Boolean = True
While running
For j As Integer = 0 To threadcount - 1
If threads(j).UpdateState = 0 Then
Thread.Sleep(10)
running = False
SecUpd(j) = ""
Interlocked.Increment(UDone) 'threadsafe method of incrementing a variable by 1
Interlocked.Exchange(UCount, UCount + threads(j).UpdateCount) 'Threadsafe method for assigning a value
End If
Next
End While
End While
Dim pending As Integer = threadcount
Dim tempcount As Integer = 0
Dim oldcount As Integer = UCount
While pending > 0
pending = threadcount
tempcount = 0
For i = 0 To threadcount - 1
If threads(i).UpdateState = 0 Then
SecUpd(i) = ""
pending -= 1
tempcount += threads(i).UpdateCount
Thread.Sleep(10)
End If
Next
Interlocked.Exchange(UDone, Me.Stocks.Count - pending) 'Threadsafe method for assigning a value
Interlocked.Exchange(UCount, oldcount + tempcount) 'Threadsafe method for assigning a value
End While
Me.URunning = False
Catch ex As System.Threading.ThreadAbortException 'handle abort correctly
Dim pending As Integer = threadcount
Dim tempcount As Integer = 0
Dim oldcount As Integer = UCount
While pending > 0
pending = threadcount
tempcount = 0
For i = 0 To threadcount - 1
If threads(i).UpdateState = 0 Then
SecUpd(i) = ""
pending -= 1
tempcount += threads(i).UpdateCount
End If
Next
Interlocked.Exchange(UDone, Me.Stocks.Count - pending) 'Threadsafe method for assigning a value
Interlocked.Exchange(UCount, oldcount + tempcount) 'Threadsafe method for assigning a value
End While
End Try
End Sub
This is fantastically simplified, but threads by their nature will run at the same time (if possible and depending to some extent on the whim of the OS). If you want less going on simultaneously, use fewer threads. In your case, since you want the jobs done sequentially, you really only want one thread.
The best approach, as mentioned, is to create a background worker that runs on a single background thread. There's usually a queue to which you submit jobs in a thread-safe way from the foreground thread. The thread proc typically runs in an infinite loop, processing each job in the queue, and going to sleep waiting for new jobs when there are none.

Visual Basic 2008 , Search for an image on my desktop ( getpixel way/ comparing pixels)

i'm a young "programmer" and i'm trying to make a code that searches if a 400 x 500 image (a bmp file from my computer ) appears on the screen .
The code looks like this :
Public Class Form1
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
'Dim-ing section'
Dim scrsize As Size = New Size(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height)
Dim scrtake = New Bitmap(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height)
Dim path As String = System.IO.Path.Combine(System.IO.Directory.GetCurrentDirectory, "fishbot.bmp")
Dim resource As New Bitmap(path)
Dim a As System.Drawing.Graphics = System.Drawing.Graphics.FromImage(scrtake)
Dim b As System.Drawing.Graphics = System.Drawing.Graphics.FromImage(resource)
a.CopyFromScreen(New Point(0, 0), New Point(0, 0), scrsize)
'as a test to see if the code is compiled until here'
Me.BackgroundImage = scrtake
'end test'
For x As Integer = 100 To scrsize.Height - 400
For y As Integer = 300 To scrsize.Width - 300
For x1 As Integer = 0 To resource.Height
For y1 As Integer = 0 To resource.Width
If scrtake.GetPixel(x, y) = resource.GetPixel(x1, y1) Then
TextBox1.Text = "found"
End If
Next
Next
Next
Next
End Sub
Also , the elements are already on the form .
For x As Integer = 100 To scrsize.Height - 400
For y As Integer = 300 To scrsize.Width - 300
For x1 As Integer = 0 To resource.Height
For y1 As Integer = 0 To resource.Width
If scrtake.GetPixel(x, y) = resource.GetPixel(x1, y1) Then
TextBox1.Text = "found"
End If
Next
Next
Next
Next
This code returns :" Parameter must be positive and < Height. Parameter name: y " and i can't find a solution . Also checked lots of sites and still nothing
And if you know a better comparation system for pixels or something to improve speed , share with me :) .
ok looked over your code
For x1 As Integer = 0 To resource.Height
should be
For x1 As Integer = 0 To resource.Height - 1
etc
a 100x100 bitmap would have the addressable area of 0-99 x 0-99 as 0 is also a valid index

My program is assigning a value to ALL objects in an array. What's happening and how do I prevent it ? (VB 2008)

I have exhausted all of my options and am very desperate for help since I cannot figure out where the bug in my code is, or if there is something I don't understand.
I'm trying to create a "methinks it is a weasel!" mimmick from Richard Dawkins' late 80s documentary about evolution. The goal is to progress through a genetic algorithm until the algorithm guesses the correct answer through mutation and fitness tournaments.
Now, here's the problem:
Private Function fitnessTourney(ByVal editGuess() As Guess, ByVal popIndex As Integer, ByVal tourneySize As Integer, ByVal popNum As Integer)
Dim randInt(tourneySize - 1) As Integer
Dim loopCount1 As Integer = 0
Dim fitnessWinner As New Guess
fitnessWinner.setFitness(-50)
...
And, this loop is where I am experiencing the critical error
...
For i = 0 To tourneySize - 1
Randomize()
randInt(i) = Int(Rnd() * popNum)
While editGuess(randInt(i)).Used = True
If loopCount1 > tourneySize Then
loopCount1 = 0
For i2 = 0 To popNum - 1
editGuess(i2).setUsed(False)
Next
i = -1
Continue For
End If
loopCount1 += 1
randInt(i) = Int(Rnd() * popNum)
End While
editGuess(randInt(i)).determineFitness(correctPhrase)
editGuess(randInt(i)).setUsed(True)
Next
For i = 0 To popNum - 1
editGuess(i).setUsed(False)
Next
What this loop is trying to do is pick out four random instances of the editGuess array of objects. This loop tries to prevent one from being used multiple times, as the population is competing to one of the 10 members (highest fitness of the 4 chosen candidates is supposed to win).
The critical error is that I mysteriously get an endless loop where any instances of editGuess(randInt(i)).Used will always evaluate to true. I have tried to fix this by resetting all instances to False if it loops too many times.
The stumper is that I'll have all instances evaluate to False in the debugger. Then, when I reach "editGuess(randInt(i)).setUsed(True)" (the exact same thing as "editGuess(randInt(i)).Used = True"), it sets this value for EVERY member of the array.
Is there anyone who can see what is happening? I am so close to completing this!
Here's the Guess class:
Public Class Guess
Dim Fitness As Integer
Dim strLength As Integer
Dim strArray(30) As String
Dim guessStr As String
Dim Used As Boolean
Public Sub New()
Fitness = 0
guessStr = ""
strLength = 0
Used = 0
End Sub
Public Sub determineFitness(ByVal correctPhrase As String)
Dim lowerVal
If guessStr.Length <= correctPhrase.Length Then
lowerVal = guessStr.Length
Else
lowerVal = correctPhrase.Length
End If
strArray = guessStr.Split("")
Fitness = 0 - Math.Abs(correctPhrase.Length - guessStr.Length)
For i = 0 To lowerVal - 1
If correctPhrase(i) = guessStr(i) Then
Fitness = Fitness + 1
End If
Next
End Sub
Public Sub Mutate(ByVal mutatepercentage As Decimal, ByVal goodLetters As String)
If mutatepercentage > 100 Then
mutatepercentage = 100
End If
If mutatepercentage < 0 Then
mutatepercentage = 0
End If
mutatepercentage = mutatepercentage / 100
If Rnd() < mutatepercentage Then
strLength = Int(Rnd() * 25) + 5
If strLength < guessStr.Length Then
guessStr = guessStr.Remove(strLength - 1)
End If
End If
For i = 0 To strLength - 1
If Rnd() < mutatepercentage Then
If i < guessStr.Length Then
guessStr = guessStr.Remove(i, 1).Insert(i, goodLetters(Int(Rnd() * goodLetters.Length)))
Else
guessStr = guessStr & goodLetters(Int(Rnd() * goodLetters.Length))
End If
End If
Next
End Sub
Public Sub setFitness(ByVal num As Integer)
Fitness = num
End Sub
Public Sub setStrLength(ByVal num As Integer)
strLength = num
End Sub
Public Sub initializeText()
End Sub
Public Sub setUsed(ByVal bVal As Boolean)
Used = bVal
End Sub
End Class
And, finally, here's where and how the function is called
Private Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer1.Tick
population1(counter) = fitnessTourney(population1, counter, 4, 10)
population2(counter) = fitnessTourney(population2, counter, 4, 10)
population1(counter).Mutate(2, goodLetters)
population2(counter).Mutate(20, goodLetters)
Label1.Text = population1(counter).guessStr
Label2.Text = population2(counter).guessStr
counter += 1
If counter > 9 Then
counter = 0
End If
End Sub
End Class
EDIT 1:
Thank you guys for your comments.
Here is the custom constructor I use to the form. This is used to populate the population arrays that are passed to the fitnessTourney function with editGuess.
Public Sub New()
InitializeComponent()
Randomize()
For i = 0 To 9
population1(i) = New Guess
population2(i) = New Guess
Next
counter = 0
correctPhrase = "Methinks it is a weasel!"
goodLetters = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ !##$%^&*()_+-=?></.,;\|`'~"
goodLettersArr = goodLetters.Split("")
For i = 0 To 9
population1(i).setStrLength(Int(Rnd() * 25) + 5)
population2(i).setStrLength(Int(Rnd() * 25) + 5)
For i2 = 0 To population1(i).strLength
population1(i).guessStr = population1(i).guessStr & goodLetters(Int(Rnd() * goodLetters.Length))
Next
For i2 = 0 To population2(i).strLength
population2(i).guessStr = population2(i).guessStr & goodLetters(Int(Rnd() * goodLetters.Length))
Next
Label1.Text = population1(i).guessStr
Label2.Text = population2(i).guessStr
Next
population1(0).guessStr = correctPhrase
population1(0).determineFitness(correctPhrase)
End Sub
I haven't studied all of your code thoroughly, but one big problem is that you are calling Randomize from within the loop. Every time you call Randomize, it re-seeds the random numbers with the current time. Therefore, if you call it multiple times before the clock changes, you will keep getting the first "random" number in the sequence using that time which will always evaluate to the same number. When generating "random" numbers, you want to re-seed your random number generator as few times as possible. Preferably, you'd only seed it once when the application starts.
As a side note, you shouldn't be using the old VB6 style Randomize and Rnd methods. Those are only provided in VB.NET for backwards compatibility. You should instead be using the Random class. It's easier to use too. With the Random class, you don't even need to call a randomize-like method, since it automatically seeds itself at the point in time when you instantiate the object. So, in the case of the Random class, the thing to be careful is to make sure that you only instantiate the object once before entering any loop where you might be using it. If you create a new Random object inside a loop, it will similarly keep generating the same numbers.