i have three functions in a module that several threads would be using. all of the functions access local variable except the main doWork sub.
Sub DoWork(byval i as integer)
synclock (ListTasks)
dim strItem as string =ListTasks(CInt(i)).ToString
end SyncLock
dim strHtml as string = GetHtml(strItem )
dim strParsed as string = ParseHtml(strHtml)
dim strResult as string = Report(strParsed )
End sub
Function GetHtml(byval url as string) as string
'code to get website
ens sub
Function ParseHtml(Byval html as string) as string
'code to parse HtmlString
end function
Function Report(Byval html as string) as string
'do the work
end function
Is this a thread safe code, so that no thread will overwrite data?
If each function are all using local variables (within the functions, each function is stateless and do not access any shared resources) and all the parameters are passed by value, so that its a separate copy in the stack ( not a reference to another object), it should be threadsafe and you really do not need any locking.
It looks like pretty much everything you're using is a local variable. Your also passing variables by value, not by reference, which helps for thread safety. So you appear to be pretty safe! Just make sure the one lock your using doesn't get you into a race condition.
Related
probably just a stupid syntax error but when I try to call a function i created in a class module I get the error message that my "objectvarable or withblock is not declarde".
Here the minimal code example from both modules:
'calling
Dim AllZyklen1 As New ArrayList
For Each Wartungsplan In ArrayWartungsplan
Set AllZyklen1 = Wartungsplan.GetAllZyklen 'added set
next Wartungsplan
'function itself
Public Function GetAllZyklen() As ArrayList
Dim AllZyklen2 As New ArrayList
'allZyklen2 gets calculated, no other functions are called just local varaibles of the class are used
If Not AllZyklen2.Contains(Zyklus) Then
AllZyklen2.Add Zyklus
end if
Set GetAllZyklen = AllZyklen2 'added set
End Function
(numbers are added to "allzyklen" just for easier reading, they are actually both called "allzyklen" without number)
Shouldnt that work? I just cant see the error.
EDIT: As for the Solution, what the answer states is absolutley correct and was necessary for my code to work. Unfortunatley I also had an spelling error for a attribute in the classmodule. In which case vba just highlights the call of this function, but no errors within the function... I ended up moving the function from the classmodule to the main module where the correct line with the spelling error got highlighted and the mistake was easier to spot.
You need to use Set for Objects (ArrayList is an object).
So it should be:
'calling
Dim AllZyklen1 As New ArrayList
For Each Wartungsplan In ArrayWartungsplan
Set AllZyklen1 = Wartungsplan.GetAllZyklen
Next Wartungsplan
and
'function itself
Public Function GetAllZyklen() As ArrayList
Dim AllZyklen2 As New ArrayList
'allZyklen2 gets calculated, no other unctions are called just local varaibles of the class are used
Set GetAllZyklen = AllZyklen2
End Function
Full example that works:
Class Module ClassWartungsplan:
Option Explicit
Public Function GetAllZyklen() As ArrayList
Dim AllZyklen2 As New ArrayList
'allZyklen2 gets calculated, no other unctions are called just local varaibles of the class are used
AllZyklen2.Add "abc"
Set GetAllZyklen = AllZyklen2
End Function
Standard Module:
Option Explicit
Sub Example()
Dim AllZyklen1 As New ArrayList
Dim Wartungsplan As New ClassWartungsplan
Set AllZyklen1 = Wartungsplan.GetAllZyklen
Debug.Print AllZyklen1(0) ' prints ABC in the immediate window
End Sub
Public Function PiesTableTest(compairFile As String, version1 As String, Optional silent As Boolean = False) As Boolean
Dim dpgs As New frmDetailProgress
Dim retturn As Boolean
PiesThreadedTableTest(compairFile, version1, silent, dpgs)
End Function
Async Function PiesThreadedTableTest(compairFile As String, version1 As String, silent As Boolean, dpgs As frmDetailProgress) As Task(Of Boolean)
Dim ctl() As xmlControlAry
Dim xmlDoc As XElement
Dim xmlNodes As IEnumerable(Of XElement)
Dim notfound(0) As String
version = version1
nodeErrors = False
If Not silent Then
dpgs.lblTital.Text = "Pies Configuration Check"
dpgs.add("Pies Version = " & version)
dpgs.add("Loading Config Data....")
dpgs.Show()
End If
' load configuration data
GetPiesControl(ctl, version)
' load test xml file
xmlDoc = XElement.Load(compairFile)
xmlNodes = xmlDoc.Elements()
For Each ele As XElement In xmlNodes
NodeDrill("", ele, ctl, dpgs, notfound, silent)
Next
If nodeErrors And Not silent Then
dpgs.add("Testing done with Errors!!!", "R")
Else
dpgs.add("Testing Done NO ERRORS!", "G")
End If
Application.DoEvents()
If silent Then
dpgs.Dispose()
End If
'PiesThreadedTableTest = Not nodeErrors
If nodeErrors Then
Return False
Else
Return True
End If
End Function
I am trying to understand multi threading. frmDetailProgress is a "please wait " kind of form. and i have a animated gif on it. Plus it has a check box to close automatically after completion. Well the form is frozen till the process is done. I am trying to get the piesthreadedtabletest to run in another thread. I have read allot on this but i just don't understand the concept. I don't understand the await function enough to make this work. i get that await is designed to stop processing until something happens. But i want that form freed up to work. I get an error saying that the function will run synchronously unless i have an await - Why?
I got it working. It was a lack of understanding and i probably still need to learn more. I hope this will help someone in the future.
i created a class to call functions in the other class running in the second thread.
imports system.threading
public sub callThreadedProcedure()
dim tp as system.threading.thread ' this will be for the object running in the other thread
dim objectToRun as myclass ' this is the object you want to run in the thread
'this gets the object and puts it into the new thread
tp = new thread(sub() objectToRun.FunctionToRun(<put your parameters here if any>))
' start execution of the object in a new thread.
tp.start()
' that will get it to run in a separate thread. It works, there might be a better way
' and might not work in all situations, but for now it fixed my problem.
end sub
if you are trying to run functions in the original thread you need to pass a
reference to that object to the one in the second thread. you must then use invoke to run a function or sub from the second thread.
Invoke(sub() obj.function(<parameters>))
thanks Idle_mind invoked worked like it should.
I appreciate all that helped me along.
I have always used Option Explicit in each module. I never gave it much thought, until now.
Code:
Option Explicit
Function ParseJSON(ByVal strJSON As String) As String
strJSON = "New String"
'MsgBox (strJSON)
ParseJSON = strJSON
End Function
Sub Test()
Dim strJSON As String
strJSON = "Old String"
MsgBox (ParseJSON(strJSON))
MsgBox (strJSON)
End Sub
This piece of code is just a test and has nothing to do with JSON. When I run the code, I expected it to throw an error as strJSON is never declared in ParseJSON, and it should be a new variable as the original one is passed ByVal and thus cannot be changed, the last MsgBox() confirms this.
Is there anything that I didn't get? My hunch points to the ByVal part., or maybe Option Explicit only checks Sub?
the point of Option Explicit is to have you explicitly declare all variables you're using, and that's what actually happens in Function ParseJSON(ByVal strJSON As String) As String: that strJSON As String is declaring variable strJSON you're going to use inside the function (and it's also declaring it as of String type)
then, you're also giving it a value passed by the calling sub, and that ByVal simply means that whatever value the function strJSON variable is going to assume it won't affect the calling sub variable (if any, and that may be incidentally named after strJSON, but it's distinct from function strJSON) you passed the value of
that's why if you try
Function ParseJSON(ByVal strJSON As String) As String
strJSON = Range("A1:A3")
'MsgBox (strJSON)
ParseJSON = strJSON
End Function
you'll get a run time type mismatch error as soon as the strJSON = Range("A1:A3") line is processed
and that's why if you try
Sub Test()
Dim strJSON As String
strJSON = "Old String"
MsgBox (ParseJSON(Range("A1:A3")))
MsgBox (strJSON)
End Sub
you'll get the same run time type mismatch error as soon as the MsgBox (ParseJSON(Range("A1:A3"))) line is processed
You ARE declaring properly.
In the Test function you declare it as a string with the DIM statement.
And by making it an argument in the function you are also declaring it as a string, for use in the function.
Because the function uses it byVal, and changes you make IN the function, will not affect the value of the string.
It you want to change the value of it, it has to be passed byRef.
ByRef effectively passed the actual variable. - It could be known by a different name even, but any changes will also change the original variable.
byVal is effectively passing a copy of the variable, which you can play about with but not change the original.
Imagine as a piece of paper.
byRef you hand over the actual piece of paper to be marked up
byVal you hand over a photocopy, but keep the original to yourself.
I'm having trouble with selecting only part of a collection and passing it by reference.
So I have a custom class EntityCollection which is , who guessed, a collection of entities. I have to send these entities over HTTPSOAP to a webservice.
Sadly my collection is really big, let's say 10000000 entities, which throws me an HTTP error telling me that my request contains too much data.
The method I am sending it to takes a Reference of the collection so it can further complete the missing information that is autogenerated upon creation of an entity.
My initial solution:
For i As Integer = 0 To ecCreate.Count - 1 Step batchsize
Dim batch As EntityCollection = ecCreate.ToList().GetRange(i, Math.Min(batchsize, ecCreate.Count - i)).ToEntityCollection()
Q.Log.Write(SysEnums.LogLevelEnum.LogInformation, "SYNC KLA", "Creating " & String.Join(", ", batch.Select(Of String)(Function(e) e("nr_inca")).ToArray()))
Client.CreateMultiple(batch)
Next
ecCreate being an EntityCollection.
What I forgot was that using ToList() and ToEntityCollection() (which I wrote) it creates a new instance...
At least ToEntityCollection() does, idk about LINQ's ToList()...
<Extension()>
Public Function ToEntityCollection(ByVal source As IEnumerable(Of Entity)) As EntityCollection
Dim ec As New EntityCollection()
'ec.EntityTypeName = source.FirstOrDefault.EntityTypeName
For Each Entity In source
ec.Add(Entity)
Next
Return ec
End Function
Now, I don't imagine my problem would be solved if I change ByVal to ByRef in ToEntityCollection(), does it?
So how would I actually pass just a part of the collection byref to that function?
Thanks
EDIT after comments:
#Tim Schmelter it is for a nightly sync operation, having multiple selects on the database is more time intensive then storing the full dataset.
#Craig Are you saying that if i just leave it as an IEnumerable it will actually work? After all i call ToArray() in the createmultiple batch anyway so that wouldn't be too much of a problem to leave out...
#NetMage you're right i forgot to put in a key part of the code, here it is:
Public Class EntityCollection
Implements IList(Of Entity)
'...
Public Sub Add(item As Entity) Implements ICollection(Of Entity).Add
If IsNothing(EntityTypeName) Then
EntityTypeName = item.EntityTypeName
End If
If EntityTypeName IsNot Nothing AndAlso item.EntityTypeName IsNot Nothing AndAlso item.EntityTypeName <> EntityTypeName Then
Throw New Exception("EntityCollection can only be of one type!")
End If
Me.intList.Add(item)
End Sub
I Think that also explains the List thing... (BTW vb or c# don't matter i can do both :p)
BUT: You got me thinking properly:
Public Sub CreateMultiple(ByRef EntityCollection As EntityCollection)
'... do stuff to EC
Try
Dim ar = EntityCollection.ToArray()
Binding.CreateMultiple(ar) 'is also byref(webservice code)
EntityCollection.Collection = ar 'reset property, see below
Catch ex As SoapException
Raise(GetCurrentMethod(), ex)
End Try
End Sub
And the evil part( at least i think it is) :
Friend Property Collection As Object
Get
Return Me.intList
End Get
Set(value As Object)
Me.Clear()
For Each e As Object In value
Me.Add(New Entity(e))
Next
End Set
End Property
Now, i would still think this would work, since in my test if i don't use Linq or ToEntityCollection the byref stuff works perfectly fine. It is just when i do the batch thing, then it doesn't... I was guessing it could maybe have to do with me storing it in a local variable?
Thanks already for your time!
Anton
The problem was that i was replacing the references of Entity in my local batch, instead of in my big collection... I solved it by replacing the part of the collection that i sent as a batch with the batch itself, since ToList() and ToEntityCollection both create a new object with the same reference values...
Thanks for putting me in the correct direction guys!
I am trying to create a variable which is of type MyReferenceTypeObject and of value null on thread one, use a delegate to make this thread equal to a new instance of MyReferenceTypeObject on thread two, and then access members of MyReferenceTypeObject back on thread one (in the delegates callback method).
My code is below:
Module Module1
Delegate Sub EditReferenceTypePropertyDelegate(ByVal referenceTypeObject As MyReferenceTypeObject, ByVal numberToChangeTo As Integer)
Sub Main()
Dim editReferenceDelegate = New EditReferenceTypePropertyDelegate(AddressOf EditReferenceTypeProperty)
Dim newReferenceTypeObject As MyReferenceTypeObject
editReferenceDelegate.BeginInvoke(newReferenceTypeObject, 2, AddressOf EditReferenceCallback, newReferenceTypeObject)
Console.ReadLine()
End Sub
Sub EditReferenceTypeProperty(ByVal referenceTypeObject As MyReferenceTypeObject, ByVal numberToChangeTo As Integer)
referenceTypeObject = New MyReferenceTypeObject()
referenceTypeObject.i = numberToChangeTo
End Sub
Sub EditReferenceCallback(ByVal e As IAsyncResult)
Dim referenceObject = DirectCast(e.AsyncState, MyReferenceTypeObject)
Console.WriteLine(referenceObject)
End Sub
End Module
Class MyReferenceTypeObject
Public Property i As Integer
End Class
However, newReferenceTypeObject comes into my callback method as null. I think I understand why, but the problem is that I need to pull some data from a database which I then need to pass into the constructor of newReferenceTypeObject, this takes a couple of seconds, and I don't want to lock up my UI while this is happening. I want to create a field of type MyReferenceTypeObject on thread one, instantiate this on thread two (after I have pulled the data of the database to pass into the constructor) and then work with members of the object back on thread one once the instantiation is complete.
Is this possible? I am using VB.Net with .Net 4.0 on Visual Studio 2012.
If you want to keep the GUI responsive during a long running action, I'd consider using the Task<> library (Comes with .NET 4.0). Here's a quick example.
Sub Main()
Dim instantiateTask = New Task(Of MyReferenceTypeObject)(Function()
' Call your database to pull the instantiation data.
Return New MyReferenceTypeObject With {.i = 2}
End Function)
instantiateTask.Start() ' Start the task -> invokes a ThreadPool.Thread to do the work.
instantiateTask.ContinueWith(Sub(x)
Console.WriteLine(x.Result.I)
End Sub, TaskScheduler.FromCurrentSynchronizationContext())
End Sub
.Wait blocks the GUI thread. However, you could use ContinueWith which is async and therefor nonblocking. Also you need to provide the TaskScheduler ( TaskScheduler.FromCurrentSynchronizationContext ) from the GUI thread to prevent cross-thread exceptions in case you want to update the UI from within the async method.