Parallel.for Loop - result varies for each click .. why does this happen? - vb.net

Following parallel.for loop uses data of a bitarray which is 300000 bits in length. and the data is fixed not to change. So the produced results "Count_of_Found_Pattern1" must be same no matter how many times I execute the function "check"
But, the issue is the values of "Count_of_Found_Pattern1" & "Count_of_Found_Pattern2" produce different values every time I execute the function "check" .. what have I done wrong?
when I check it using small amount of bits (about 16 bits instead of 300000) it produces good results. But, when the bitarray length is lengthier, it produces a total mess.
For Example:
1st execution --> Count_of_Found_Pattern1 = 150526 , Count_of_Found_Pattern2 = 97855
2nd execution --> Count_of_Found_Pattern1 = 45855 , Count_of_Found_Pattern2 = 187562
Regards!
Private Function check()
Dim Count_of_Found_Pattern1 As Int64 = 0
Dim Count_of_Found_Pattern2 As Int64 = 0
Dim stopwatch As New Stopwatch
stopwatch.Start()
Dim Current_Position1 As Int64 = 0
Dim Current_Position2 As Int64 = 1
Parallel.For(0, lastbitarrayover2, Sub(countbits, loopstate)
If BitArray(Current_Position1) = False And BitArray(Current_Position2) = True Then
Count_of_Found_Pattern1 = Count_of_Found_Pattern1 + 1
End If
If BitArray(Current_Position1) = True And BitArray(Current_Position2) = False Then
Count_of_Found_Pattern1 = Count_of_Found_Pattern1 + 1
End If
If BitArray(Current_Position1) = True And BitArray(Current_Position2) = True Then
Count_of_Found_Pattern2 = Count_of_Found_Pattern2 + 1
End If
If BitArray(Current_Position1) = False And BitArray(Current_Position2) = False Then
Count_of_Found_Pattern2 = Count_of_Found_Pattern2 + 1
End If
Current_Position1 = Current_Position1 + 2
Current -Position2 = Current_Position2 + 2
Numer_of_Completed_Iterations = Numer_of_Completed_Iterations + 1
End Sub)
Numer_of_Completed_Iterations = 0 'reset counter to 0
stopwatch.Stop()
TextBox1.Text = stopwatch.Elapsed.ToString
End Function

When you increment Count_of_Found_Pattern1 (or Pattern2), first the value is read, then it is incremented, then it assigned. But the thread that is executing can change during those three steps.
Thread 1: Read Count_of_Found_Pattern1
Thread 2: Read Count_of_Found_Pattern1
Thread 2: Increment
Thread 2: Write to Count_of_Found_Pattern1
...
Thread 1: Increment its old value
Thread 1: Write to Count_of_Found_Pattern1
Now Count_of_Found_Pattern1 is wrong. And if Thread 2 had control of execution for more than just one iteration, it could be very wrong.
Consider doing as much as possible within PLINQ, avoiding any mutation of global state until all the threads have joined back up. The following code assumes you're interested in comparing adjacent entries in BitArray:
Dim counts = Enumerable.
Range(0, lastbitarrayover2 / 2).Select(Function(i) i * 2).
AsParallel().
Aggregate(
Function()
' We're using an object of anonymous type to hold
' the results of the calculation so far.
' Each thread calls this function, so each thread
' gets its own object for holding intermediate results
Return New With {.Pattern1 = 0, .Pattern2 = 0, .Iterations = 0}
End Function,
Function(accumulator, i)
' accumulator is this thread's intermediate-result-holder.
' i is one of the even numbers from our big set of even numbers.
' Each thread will call this function many times, building up its own accumulator object
' the four conditionals from your code reduce to this If block
If (BitArray(i) = BitArray(i + 1)) Then
accumulator.Pattern2 += 1
Else
accumulator.Pattern1 += 1
End If
accumulator.Iterations += 1
Return accumulator
End Function,
Function(acc1, acc2)
' Once each thread has built up its own accumulator object,
' this function makes a new accumulator object that
' combines the results from two threads.
' This is called repeatedly until all the threads' results
' have been combined.
Return New With {
.Pattern1 = acc1.Pattern1 + acc2.Pattern1,
.Pattern2 = acc1.Pattern2 + acc2.Pattern2,
.Iterations = acc1.Iterations + acc2.Iterations}
End Function,
Function(acc)
' The last function here is supposed to take the combined results
' and turn them into what you ultimately want to use.
' Since the combined results are already in the form we want,
' we'll just use the "identity" function here: it returns its
' argument unchanged
Return acc
End Function)
Count_of_Found_Pattern1 = counts.Pattern1
Count_of_Found_Pattern2 = counts.Pattern2
Number_of_Completed_Iterations = counts.Iterations
This might seem like a lot, but it's really not too bad. The main thing is that we are giving each thread its own set of variables to work with; that way we don't have to worry about the problems I outlined at the top of my answer. Then, we combine the work done by each thread at the end.

Have you tried System.Threading.Interlocked.Increment to make this thread safe? For example:
If BitArray(Current_Position1) = False And BitArray(Current_Position2) = True Then
Interlocked.Increment(Count_of_Found_Pattern1)
End If

Related

Do-While loop (VBA) not looping

so I thought this would be a simple logical problem, but for the life of me I cannot find the issue with this code block. I have checked around on Stack for a solution, but all other do/while loop problems appear to be primarily with other languages.
What I am trying to do is simply loop through an array & add a new worksheet for each element in the array that is not null. Pretty simple right? Yet for some reason it simply loops through once and thats it.
Here is the code block:
Dim repNames() As String
Dim x As Integer
x = 25
ReDim repNames(1 To x)
repNames(1) = "Ahern"
repNames(2) = "Castronovo"
repNames(3) = "Glick"
repNames(4) = "Fields"
repNames(5) = "Murphy"
repNames(6) = "Sleeter"
repNames(7) = "Vivian"
repNames(8) = "Walschot"
repNames(9) = "Wilson"
Dim i As Integer
i = 1
Do 'Loop keeps creating only 1 new sheet. Should create 9.
Worksheets.Add.Name = repNames(i)
i = i + 2
Loop While repNames(i) <> Null
I believe the problem is with this line: Loop While repNames(i) <> Null,
but obviously the logical test seems to hold up.
Any help would be hugely appreciated!
As others note, Null is not the comparison you want to make. Testing anything for equivalence with Null will return Null -- even ?Null = Null returns Null, which is why your loop is exiting early. (Note: To test for a Null, you need to use the IsNull function which returns a boolean, but that is NOT how you test for an empty string.)
In VBA, to test for a zero-length string or empty string, you can use either "" or vbNullString constant, or some people use the Len function to check for zero-length.
Rectifying that error, as originally written in your code, your logical test should abort the loop if any item is an empty string, but none of the items are empty strings (at least not in the example data you've provided) so you end up with an infinite loop which will error once i exceeds the number of items in the repNames array.
This would be probably better suited as a For Each loop.
Dim rep as Variant
For Each rep in repNames
Worksheets.Add.Name = rep
Next
If you need to skip empty values, or duplicate values, you can add that logic as needed within the loop:
For Each rep in repNames
If rep <> vbNullString 'only process non-zero-length strings
Worksheets.Add.name = rep
End If
Next
Etc.
Firstly, you should be comparing to vbNullString. This loops multiple times:
' Declare variables
Dim repNames() As String
Dim x As Integer
Dim i As Integer
' Set data
x = 25
ReDim repNames(1 To x)
repNames(1) = "Ahern"
repNames(2) = "Castronovo"
repNames(3) = "Glick"
repNames(4) = "Fields"
repNames(5) = "Murphy"
repNames(6) = "Sleeter"
repNames(7) = "Vivian"
repNames(8) = "Walschot"
repNames(9) = "Wilson"
' Loop through items
i = 1
Do
Worksheets.Add.Name = repNames(i)
i = i + 2
Loop While repNames(i) <> vbNullString
There is one more problem – why i = i + 2 ? In your question you say you wanted the loop to execute 9 times, but using i = i + 2 skips every other item. If you indeed want to loop through every item:
Do
Worksheets.Add.Name = repNames(i)
i = i + 1
Loop While repNames(i) <> vbNullString
Here you go, I have changed the loop conditional, and changed i=i+2 to i=i+1. A regular while loop would be better than a do while encase the first element is empty
Dim repNames()
Dim x As Integer
x = 25
ReDim repNames(1 To x)
repNames(1) = "Ahern"
repNames(2) = "Castronovo"
repNames(3) = "Glick"
repNames(4) = "Fields"
repNames(5) = "Murphy"
repNames(6) = "Sleeter"
repNames(7) = "Vivian"
repNames(8) = "Walschot"
repNames(9) = "Wilson"
Dim i As Integer
i = 1
Do While repNames(i) <> ""
Worksheets.Add.Name = repNames(i)
i = i + 1
Loop

Recursive function to merge 3D objects

I have several 3D items in listOri. For this example:
listOri has A,B,C,D,E.
A overlaps with C.
B overlaps with D.
D overlaps with E.
I have a recursive function which accepts listOri, check if each item overlaps with each other, and generates a final listNew which has AC, BDE.
Iteration 1:
Loop through each item in listOri, generates listNew containing AC,B,D,E
Iteration 2:
Loop through AC,B,D,E in listNew, generates (new) listNew containing, AC,BD,E
Iteration 3: and so on.
Here is the snippet code which check if each 3D object in a list overlaps, and produces a new list recursively.
Private Function SimplifyModel2(ByVal listOri As List(Of Mesh3D)) As List(Of Mesh3D)
Dim listNew As New List(Of Mesh3D)(listOri)
Dim indexOut, indexIn, indexInner, PercentProgressCurrent As Integer
Dim currentMeshOutter, currentMeshInner As Mesh3D
Dim isExitForCalled As Boolean = False
totInnerLoops = totInnerLoops + 1 ' increment total number of inner loops
For indexOut = 0 To (listOri.Count - 1)
currentMeshOutter = listOri(indexOut)
indexInner = indexOut + 1
For indexIn = indexInner To (listOri.Count - indexInner)
currentMeshInner = listOri(indexIn)
If Is3DOverlap(currentMeshInner, currentMeshOutter) = True Then
currentMeshOutter.CombineMerge(currentMeshInner)
listNew.Remove(currentMeshInner)
listNew.Remove(currentMeshOutter)
listNew.Insert(0, currentMeshOutter)
listNew = SimplifyModel2(listNew) ' recursively call the function
isExitForCalled = True
Exit For
End If
Next
If isExitForCalled = True Then
Exit For
End If
Next
indLoopExit = indLoopExit + 1
Return listNew
End Function
The function works well with listOri with very few items.
However, when there are thousands of 3D items in listOri, the functions takes very long time to produce the listNew.
How do I increase the speed of the recursive function?
Is there another way to write an algorithm which performs the same task above?
Let me know if you need any information.
Thank you.
I have found the solutions from the Code Review StackExchange.
Please refer to the link below:
Recursive function to merge 3D objects

How to enable/disable button with a function

I have a problem with my university project
It's a little game, 6 buttons for each players and 2 players so 12 buttons
There is number in each buttons, if a player has his 6 buttons at 0, he can't play
I have try some Public Function and i'm actually working with a very simple one but i think this is not the problem
My function is here
And in my form, the problem is here, i've tried many things but i don't know how do to that ... I read my lesson and I'm searching on the internet, i have no idea ..
If possible is True you don't re-enable the button.
You can simplify things.
Public Function PeutJouer(ByVal joueur As Integer) As Boolean
Dim sum As Integer
Dim start As Integer = (joueur - 1) * 7
For i As Integer = start To start + 5
sum += tableau(i)
Next
Return sum <> 0
End Function
Then
Btn1P1.Enabled = PeutJouer(1)
Did you show all the relevant code? You are declaring Dim tableau(12) As Integer but the array is never filled with values. Probably tableau should be declared at the form level and not locally in this function. If you already have both, remove the local declaration, because it hides the one at form level. You also need to return the result from the function. I don't see this in your function.
Note that this
If x <> 0 Then
booleanVariable = True
Else
booleanVariable = True
End If
can be simplified to
booleanVariable = x <> 0
i.e., the condition is an expression yielding the Boolean result True or False already and you can use this value directly. When working with numeric values you don't write If x + y = 1 Then r = 1 Else If x + y = 2 Then r = 2 .... You simply write r = x + y.

For Loop: Skip to next line after X seconds

Can someone help me with this code:
I have a dataGrid with 2 columns:
and what I want to do is use PStools' Psloggedon cmd to give me the name of every person logged in and append that result to the "LOGGED_IN" column but what is happening is that if there is no user logged into a PC, the process takes like 5 minutes to post an error message.
Now, what I want to do is that if .5 seconds has gone to just forget the row it's currently querying and move on to the next row, in the column?
here is the vb.net code i want to focus on:
Dim RowCount As Integer = datagridView1.RowCount
For i = 0 To RowCount - 2
'PERFORM PSLOGGEDON ROUTINE
Dim Proc1 As New Process
Proc1.StartInfo = New ProcessStartInfo("psloggedon")
Proc1.StartInfo.Arguments = "-l \\" & datagridView1.Rows(i).Cells(0).Value & ""
Proc1.StartInfo.RedirectStandardOutput = True
Proc1.StartInfo.UseShellExecute = False
Proc1.StartInfo.CreateNoWindow = True
Proc1.Start()
'INSERT RESULTS IN LOGGEN_IN COLUMN
datagridView1.Rows(i).Cells(1).Value = Proc1.StandardOutput.ReadToEnd
Next
Can someone please show me how to write the code to get that done?
Use Process.WaitForExit(int milliseconds) method.
Instructs the Process component to wait the specified number of milliseconds for the associated process to exit.
Return Value
Type: System.Boolean
true if the associated process has exited; otherwise, false.
You can then use Process.Kill to kill process if it did not exit in given time.
Something like
Dim RowCount As Integer = datagridView1.RowCount
For i = 0 To RowCount - 2
'PERFORM PSLOGGEDON ROUTINE
Dim Proc1 As New Process
Proc1.StartInfo = New ProcessStartInfo("psloggedon")
Proc1.StartInfo.Arguments = "-l \\" & datagridView1.Rows(i).Cells(0).Value & ""
Proc1.StartInfo.RedirectStandardOutput = True
Proc1.StartInfo.UseShellExecute = False
Proc1.StartInfo.CreateNoWindow = True
Proc1.Start()
If Not Proc1.WaitForExit(5000) Then
Proc1.Kill()
End If
'INSERT RESULTS IN LOGGEN_IN COLUMN
datagridView1.Rows(i).Cells(1).Value = Proc1.StandardOutput.ReadToEnd
Next

How to not generate a stack overflow when a sub procedure calls itself?

This code generates a stack overflow. I'm aware it is caused by the procedure calling itself.
What can I do to avoid the stack overflow? Recalling the sub procedure and generating a new random number is the easiest thing to do, however it generates the overflow. The randomly generated number picks a random inventory item, then the if statement matches that number (random inventory item) with the quantity of that item from the deck inventory to make sure it isn't less than 1. If the inventory of that item is 0, the else plays and restarts the procedure, generating a new random number and doing the process all over again. In another procedure I have a function that if the deck's inventory becomes completely empty, then the discard pile replenishes the deck, making the discard pile empty, so there should never be a case where all randomly generated numbers can be associated item with a inventory of 0.
I wonder if I could somehow force the random number generator
Number = (DeckGroup(Rnd.Next(0, DeckGroup.Count)).ID)
not to generate numbers to inventory items DeckGroup(Number).QuantityInteger that are zero. By doing so I wouldn't even need to recall the function.
The random number is generated by a different branch in the same structure group.
Private Sub PlayElse()
Dim CardCheckBoxArray() As CheckBox = {CardCheckBox1, CardCheckBox2, CardCheckBox3, CardCheckBox4, CardCheckBox5}
'Reset Number Generator
Number = (DeckGroup(Rnd.Next(0, DeckGroup.Count)).ID)
Dim PlayerQuantitySubtractionInteger As Integer
For PlayerQuantitySubtractionInteger = ChecksDynamicA To ChecksDynamicB
If CardCheckBoxArray(TextBoxInteger).Checked = True And DeckGroup(Number).QuantityInteger > 0 Then
DeckGroup(Number).QuantityInteger -= 1
'Select the Player depending value of T
Select Case T
Case 0
Player1HandGroup(Number).QuantityInteger += 1
Case 1
Player1HandGroup(Number).QuantityInteger2 += 1
Case 2
Player1HandGroup(Number).QuantityInteger3 += 1
Case 3
Player1HandGroup(Number).QuantityInteger4 += 1
Case 4
Player1HandGroup(Number).QuantityInteger5 += 1
End Select
CardTypeArray(PlayerQuantitySubtractionInteger) = Player1HandGroup(Number).CardType
CardCheckBoxArray(TextBoxInteger).Text = Player1HandGroup(Number).CardNameString
NumberArray(PlayerQuantitySubtractionInteger) = Number
Else
If CardCheckBoxArray(TextBoxInteger).Checked = True And DeckGroup(Number).QuantityInteger < 0 Then
Call PlayElse()
End If
End If
Next PlayerQuantitySubtractionInteger
End Sub
You could use LINQ to weed out all the objects you never want to get first and then use the collection returned by the linq instead of your original collection.
Something like:
Private Sub PlayElse()
Dim CardCheckBoxArray() As CheckBox = {CardCheckBox1, CardCheckBox2, CardCheckBox3, CardCheckBox4, CardCheckBox5}
'Reset Number Generator
Dim temp As IEnumerable(Of LunchMoneyGame.LunchMoneyMainForm.Group) = From r In DeckGroup Where r.QuantityInteger > 0 Select r
If temp IsNot Nothing AndAlso temp.Any Then
Number = (temp(Rnd.Next(0, temp.Count)).ID)
' ** Edit **: This will ensure that you only got 1 object back from the LINQ which can tell you whether or not you have bad data. You *can* exclude this check but its good practice to include it.
Dim obj As LunchMoneyGame.LunchMoneyMainForm.Group = Nothing
Dim t = From r In temp Where r.ID = Number Select r
If t IsNot Nothing AndAlso t.Count = 1 Then
obj = t(0)
End If
If obj IsNot Nothing Then
Dim PlayerQuantitySubtractionInteger As Integer
For PlayerQuantitySubtractionInteger = ChecksDynamicA To ChecksDynamicB
' ** Edit **
obj.QuantityInteger -= 1
'Select the Player depending value of T
Select Case T
Case 0
Player1HandGroup(Number).QuantityInteger += 1
Case 1
Player1HandGroup(Number).QuantityInteger2 += 1
Case 2
Player1HandGroup(Number).QuantityInteger3 += 1
Case 3
Player1HandGroup(Number).QuantityInteger4 += 1
Case 4
Player1HandGroup(Number).QuantityInteger5 += 1
End Select
CardTypeArray(PlayerQuantitySubtractionInteger) = Player1HandGroup(Number).CardType
CardCheckBoxArray(TextBoxInteger).Text = Player1HandGroup(Number).CardNameString
NumberArray(PlayerQuantitySubtractionInteger) = Number
Next PlayerQuantitySubtractionInteger
End If
End If
End Sub
Pass through the list and determine only those that are valid. Then randomly pull from that set. Here is a simple version of it. You could use LINQ as well, but this should be clear enough:
Dim validDeckGroupsIndexes As New List(Of Integer)
For ndx As Integer = 0 to DeckGroup.Count - 1
If DeckGroup(ndx).QuantityInteger > 0 Then
validDeckGroupsIndexes .Add(ndx)
End If
Next ndx
Then use this:
Dim deckGroupNdx As Integer = Rnd.Next(0, validDeckGroupsIndexes.Count)
Number = DeckGroup(deckGroupNdx).ID