List Of Multithreading throwing exception - vb.net

I currently have a thread which at a stage goes through a List(of CustomClass) constantly with a ForEach loop. My problem is if I try to modify that list from the UI thread it throws a:
Collection was modified; enumeration operation may not execute
I tried using SyncLock which clearly doesn't work the way I thought, I also tried this:
Dim TempList As System.Collections.ObjectModel.ReadOnlyCollection(Of CustomClass) = G_.ActiveEnts.AsReadOnly
For each _Element in TempList
'Do stuff
Next
And other variations of it, like converting to an array

Of course after about 5 seconds of writing this question I decided to put a SyncLock on every access to the List of, rather than just in the thread. So whenever I modify the List of it SyncLocks it and this fixed it.

Related

Multithreading Webbrowsers

I am currently making a vb program that i plan to make very big. I have a decent knowledge of visual basic but today i came across something i do not understand. Because of the huge size of my program , i decided to try and keep the program as organized as possible by putting specific subs in modules. These subs consist of httprequest , webbrowsers(control), webclients and alot of loops. In order to prevent these subs from lagging my main application i thread them using threading.thread and i start them from my main form. But this leads to two problems.
Problem 1: The threads cannot in any way interact with the main form.
Once the a httprequest or webclient collects the information from my desired website, i am trying to make it add the info to a listbox in my main form, So what i did is it typed
Msgbox("Info Sent")
form1.listbox1.items.add(String)
The first messagebox will show but although the code right under it runs, nothing is added to the first forms listbox.I am not using delegates to transfer the information, instead, although its not a good habit, i am using checkforillegalcrossovers.
Problem 2: Threading with a webbrowser.
Threading with a webbrowser using threading.thread also does not work because it causes an active x error. After looking it up i found that a solution was to use a single threaded apartment but this would not work because i may need multiple threads running off the same sub at once.
One solution that i have found to this problem is creating another form completely and setting it invisible, and since the form is its own thread i do not need to use threading.thread , but the problem comes when i am trying to create multiple threads, or else i can somehow dynamically create the threads and put the subs inside of it programically this method wont work And even if it does i feel that it is sloppy so i will leave this for one of two last resorts.
The other solution is the most simple one in which i just put all of the code in the main form, but if i keep on doing that form1 is gonna become huge and sloppy, doing this wont solve the webbrowser problem either and even when using regions i still feel that something that 1000+ lines deserves its own class.
There must be some solution out there that solves these problems. Any help would be appreciated, Thanks.
I checked my code for updating the progress bar, and using a single thread with synclock will NOT work. They way I make it work is perform the step of the pbar each time after a thread is started as I have limited total threads (say less than 5 threads). Thus, even the progress bar steps before the threads are finished, but it will not progress further before new threads started. It is not 100% accurate but it more or less telling the progress
'update the progress bar
some_form.PBar1.PerformStep()
' This while loop is to count the existing running thread,
' and determine whether new thread should start
While 1
Dim t2 = New System.Threading.Thread(Sub() WaitForPermission())
t2.Start()
t2.Join()
If proceed_gen Then
Exit While
End If
End While
'Start doing what I need to do
Dim t1 = SomeSub()
t1.Start()
'End of code, as VB doest not have thread.detach()
Correct me if I am wrong, but you probably have to use a background worker. I know this is annoying, but this is the limitation of VB.net.
Or, you can have something like this (pseudo code, not tested)
structure some_struct
'define the strings you want to update, and their status such that
'main() knows if you need to update the stuff to the form
' You can also put how many threads are running, and the status of each thread,
'such that the main knows if all threads are completed
end structure
sub your_sub()
'Manipulate the website, and update the data structure with
'proper stuff you need
end sub
sub main(){
dim t1 = New System.Threading.Thread(Sub() your_sub())
t1.start()
' I am listing only one threads here, but start as many as you want
'check if there are strings that you need to update to the form
while 1
'check if there are any stuff you want to update from the data structure.
' Make sure you use synclock on the data structure, so each thread won't fight each other on accessing the data struct
dim should_update as boolean = false
should_update = 'Something thatyou should implement to judge whether you should update the form.
'You can start a thread and join it so the thread won't fight with other threads for accessing the data structure
dim some_string as string
if should_update
some_string = 'You may also need a thread to join to get the stuff you need. Dim the string as an array if it is required.
'You can also try pass by ref
'if you need to use thread to access the data structure to know if you need to update the form
form1.listbox1.items.add(some_string )
end if
end while
end sub
This is an ugly solution, but it will help you do the job...

Looping through the copy of a list and still getting an InvalidOperationException

I have a Sub that is called very frequently, while at the same time a secondary thread change the values in all the Lists it uses. Obviously, that would cause an InvalidOperationException, so I put a .ToList in the For Each loop so it copies the List before looping through it. That didn't work, and I was getting InvalidOperationException: "Collection was modified; enumeration operation may not execute." (and by the way, I don't know why, but this exception is only thrown when running the app outside of VS, making it very difficult to debug. Note that the error still exists when debugging the app on VS, only the exception isn't thrown). Then I tried explicitly copying the List, with no success, the error still persists. This is the faulty Sub:
Private Sub MasmorraPictureBox_Paint(sender As Object, e As PaintEventArgs) Handles MasmorraPictureBox.Paint
Dim c As New Integer
Dim DGridCopy As List(Of Point) = DGrid.ToList
Dim DPointsCopy As List(Of Point) = DPoints.ToList
Dim DGridColCopy As List(Of Brush) = DGridCol.ToList
Dim DPointsTextCopy As List(Of String) = DPointsText.ToList
For Each Point As Point In DGridCopy
e.Graphics.SmoothingMode = Drawing2D.SmoothingMode.HighQuality
e.Graphics.FillRectangle(DGridColCopy(c), Point.X, Point.Y, 35, 35)
c += 1
Next
c = 0
For Each Point As Point In DPointsCopy
e.Graphics.FillEllipse(Brushes.Blue, Point.X - 5, Point.Y - 5, 10, 10)
e.Graphics.DrawString(DPointsTextCopy(c), MyFont, Brushes.Black, Point.X - 5, Point.Y + 10, StrFormat)
c += 1
Next
End Sub
What is the problem?
UPDATE: The problem was already corrected by declaring the copies as .ToList of the original lists. I thought it wasn't because there was still some errors, but as I said previously, the debugger isn't throwing InvalidOperationException's (even though they were ocurring), so I only discovered the error wasn't in this Sub anymore when I ran the app outside of VS in order to discover which line was throwing it as asked in the comments.
You cannot write to a list while reading from it at the same time. That's a race. ToList counts as a reader because anything looking at the list is a reader.
Use proper synchronization like locks. Or immutable structures.
For Each loops call the enumerator of the list and enumerators don't like to be enumerated while the underlying collection is being changed (InvalidOperationException is thrown if this happens). Accessing list items directly does not throw an exception, only using the lists enumerator does.
Replace the For Each loops by simple For loops and access the list items through the indexer. As long as no items are removed from the lists, you should not tap into an invalid index. But of course the contents of the list might change under way, so this is not really thread safe.
The remarks in IEnumerator.MoveNext Method say:
An enumerator remains valid as long as the collection remains
unchanged. If changes are made to the collection, such as adding,
modifying, or deleting elements, the enumerator is irrecoverably
invalidated and the next call to MoveNext or Reset throws an
InvalidOperationException.

Parallel.ForEach gives different results each time

Please help me to convert following loop to Parallel loop. I tried using Parallel.ForEach and ConcurrentBag instead of HashSet, but the wied thing is that "Matched" returns every time different results.
I can't figure it out... Is it because of Thread Safety issues?
Keywords list contains about 500 unique strings, each 1-3 words in lenght.
Items contains about 10000 items.
Original code:
Dim Items As IEnumerable(Of Item) = Db.Items.GetAll
Dim Keywords As HashSet(Of String)
Dim Matched As HashSet(Of Item)
For Each Item In Items
For Each Keyword In Keywords
If Regex.IsMatch(Headline, String.Format("\b{0}\b", Keyword), RegexOptions.IgnoreCase Or RegexOptions.CultureInvariant) Then
If Not Matched.Contains(Item) Then
Matched.Add(Item)
End If
End If
Next
Next
Attempt to convert it to
Dim Items As IEnumerable(Of Item) = Db.Items.GetAll
Dim Keywords As HashSet(Of String)
Dim Matched As Concurrent.ConcurrentBag(Of Item)
Threading.Tasks.Parallel.ForEach(Of Item)(Items, Sub(Item)
For Each Keyword In Keywords
If Regex.IsMatch(Item.Title, String.Format("\b{0}\b", Keyword), RegexOptions.IgnoreCase Or RegexOptions.CultureInvariant) Then
If Not Matched.Contains(Item) Then
Matched.Add(Item)
End If
Continue For
End If
Next
End If
Yes, your code certainly isn't thread-safe. Using thread-safe collections won't make your code automatically thread-safe, you still need to use them correctly.
Your problem is that after Contains() finishes but before Add() is called on one thread, Add() can be called on another thread (and the same can possibly also happen while Contains() executes).
What you need to do is to:
Use locking (this means you won't need to use a thread safe collection anymore); or
Use something like ConcurrentHashSet. There is no such class in .Net, but you can use ConcurrentDictionary instead (even if it doesn't fit your needs exactly). Instead of your call to Contains() and then Add(), you could do Matched.TryAdd(Item, True), where True is there just because ConcurrentDictionary needs some value.

Should I double check before and after locking a list?

I have an in-application service which allows me to feed it messages from various sources, which will be put into a simple list. The service, running in its own thread, will, periodically, process all messages in the list into various files; one file for each source, which are then managed for size.
My question is about the proper way to check for messages and performing a lock around the code which accesses the list. There are only two places which access the list; one is where a message is added to the list and the other is where the messages are dumped from the list into a processing list.
Adding a message to the list:
Public Sub WriteMessage(ByVal messageProvider As IEventLogMessageProvider, ByVal logLevel As EventLogLevel, ByVal message As String)
SyncLock _SyncLockObject
_LogMessages.Add(New EventLogMessage(messageProvider, logLevel, Now, message))
End SyncLock
End Sub
Processing the list:
Dim localList As New List(Of EventLogMessage)
SyncLock _SyncLockObject
If (_LogMessages.Count > 0) Then
localList.AddRange(_LogMessages)
_LogMessages.Clear()
End If
End SyncLock
' process list into files...
My questions are: should I do a double check when I am processing the list, see below? And why? Or why not? And are there any dangers in accessing the list’s count property outside of the lock? Are either of the methods better or more efficient? And why? Or why not?
Dim localList As New List(Of EventLogMessage)
If (_LogMessages.Count > 0) Then
SyncLock _SyncLockObject
If (_LogMessages.Count > 0) Then
localList.AddRange(_LogMessages)
_LogMessages.Clear()
End If
End SyncLock
End If
' process list into files...
I understand that in this particular case, it may not matter if I do a double check given the fact that, outside of the processing function, the list can only grow. But this is my working example and I’m trying to learn about the finer details of threading.
Thank you in advance for any insights…
After some further research, thank you 'the coon', and some experimental programming, I have some further thoughts.
Concerning the ReaderWriterLockSlim, I have the following example which seems to work fine. It allows me to read the number of messages in the list without interfering with other code which may be trying to read the number of messages in the list, or the messages themselves. And when I desire to process the list, I can upgrade my lock to write mode, dump the messages into a processing list and process them outside of any read/write locks, thus not blocking any other threads which may want to add, or read, more messages.
Please note, that this example uses a simpler construct for the message, a String, as opposed to the previous example which used a Type along with some other metadata.
Private _ReadWriteLock As New Threading.ReaderWriterLockSlim()
Private Sub Process()
' create local processing list
Dim processList As New List(Of String)
Try
' enter read lock mode
_ReadWriteLock.EnterUpgradeableReadLock()
' if there are any messages in the 'global' list
' then dump them into the local processing list
If (_Messages.Count > 0) Then
Try
' upgrade to a write lock to prevent others from writing to
' the 'global' list while this reads and clears the 'global' list
_ReadWriteLock.EnterWriteLock()
processList.AddRange(_Messages)
_Messages.Clear()
Finally
' alway release the write lock
_ReadWriteLock.ExitWriteLock()
End Try
End If
Finally
' always release the read lock
_ReadWriteLock.ExitUpgradeableReadLock()
End Try
' if any messages were dumped into the local processing list, process them
If (processList.Count > 0) Then
ProcessMessages(processList)
End If
End Sub
Private Sub AddMessage(ByVal message As String)
Try
' enter write lock mode
_ReadWriteLock.EnterWriteLock()
_Messages.Add(message)
Finally
' always release the write lock
_ReadWriteLock.ExitWriteLock()
End Try
End Sub
The only problem I see with this technique is that the developer must be diligent about acquiring and releasing the locks. Otherwise, deadlocks will occur.
As to whether this is more efficient than using a SyncLock, I really could not say. For this particular example and its usage, I believe either would suffice. I would not do the double check for the very reasons ‘the coon’ gave about reading the count while someone else is changing it. Given this example, the SyncLock would provide the same functionality. However, in a slightly more complex system, one where multiple sources might read and write to the list, the ReaderWriterLockSlim would be ideal.
Concerning the BlockingCollection list, the following example works like the one above.
Private _Messages As New System.Collections.Concurrent.BlockingCollection(Of String)
Private Sub Process()
' process each message in the list
For Each item In _Messages
ProcessMessage(_Messages.Take())
Next
End Sub
Private Sub AddMessage(ByVal message As String)
' add a message to the 'global' list
_Messages.Add(message)
End Sub
Simplicity itself…
Theory:
Once a thread acquires the _SyncLockObject lock all other threads reentering that method will have to wait for the lock to be released.
So the check before and after the lock is irrelevant. In other words, it will have no effect. It is also not safe, because you're not using a concurrent list.
If one thread happens to check the Count in the first test while another is clearing or adding to the collection, then you'll get an exception with Collection was modified; enumeration operation may not execute.. Also, the second check can only be executed by one thread at a time (since it's synced).
This applies for your Add method as well. While the lock is owned by one thread (meaning the execution flow has reached that line), no other threads will be able to process or add to the list.
You should be careful to also lock if you are just reading from the list in some other places in your application. For more complex read/write scenarios (such as a custom concurrent collection), I recommend using ReaderWriterLockSlim.
Practice:
Use a BlockingCollection, since it is thread safe (i.e. it handles concurrency internally).

threading not mastered yet..still having ioexception errors

I have been creating multiple background threads to parse xml files and recreate new xml files. Now the problem I am having is that even though I use synclock on global variables, I will still at times get errors and I am sure that this is just the crude way of coding I am doing, but I was wondering if someone had a better option.
program flow =
access local folder and upload all files into list
strip each file into xml entries and put these entries into an arraylist
parse for specific values and enter these values into a database table
now create a thread and take the arraylist of entries and the thread will reparse
thread parses and creates a new xml file
main thread continues with another function and then goes and get a file from list
I will add some code to show problem areas but if I have declared global variable in use does the different threads overwrite that value in the variable causing contamination.
For Each g In resultsList
gXmlList.Add(g)
Next
Dim bgw As New BackgroundWorker
bgw.WorkerSupportsCancellation = True
AddHandler bgw.DoWork, New DoWorkEventHandler(AddressOf createXML)
AddHandler bgw.RunWorkerCompleted, AddressOf WorkComplete
threadlist.Add(bgw)
bgw.RunWorkerAsync()
Private Sub createXML()
num += 1
Dim file As String = Module1.infile
xmlfile = directoryPath & "\New" & dateTime.Now.ToUniversalTime.ToString("yyyyMMddhhmmss") & endExtension
Thread.Sleep(2000)
Dim doc As XmlDocument = New XmlDocument
**xwriter = New XmlTextWriter(xmlfile, Encoding.UTF8)** this is where ioexception error
xwriter.Formatting = Formatting.Indented
xwriter.Indentation = 2
xwriter.WriteStartDocument(True)
xwriter.WriteStartElement("Posts")
I have global variables through out the app and should I be locking each one and does this not make using threads then useless.
Dim j As Integer = 0
I believe your biggest problem is not knowing what features in .Net are thread safe. A list for example is not (a dictionary is). While you may get away with it you will eventually run into problems with locking, etc.
Your using classes and variables that are not thread safe. Any time you are working with threads you have to be Extremely careful with locking. To answer your question, yes, you have to lock and unlock everything you are working with unless the type / method specifically handles it for you.
There are a lot of multi threading (PLINQ for example) in .Net 4.0 which handle a lot of the "grunt work" for you. While you should learn and understand how to do thread safe code yourself it will give you a head start.
Try passing the data into the createXML() method. That may help isolate the code from other data being accessed. I would suggest reading up on threading and learning how to do it without a background worker.
Global variables are generally a bad idea. Given your VB code I'm guessing this is a carry over from the VB6 world for you. That's not in any way intended to be insulting, just trying to help advance your skills forward. Variable scope should be as confined as possible.
Another thought looking at your code is to learn how to use String.Format() when building strings / paths.
Simple manual thread in VB to get you started:
Dim bThread As New Threading.Thread(AddressOf createXML)
bThread.IsBackground = True
bThread.Start()
Well if you are having issues with thread locking then you can simply wrap your action in the following manor.
'This will need to be out of scope so that all threads have access to it
Dim readerWriterLock As New Threading.ReaderWriterLockSlim
readerWriterLock.EnterWriteLock()
xwriter = New XmlTextWriter(xmlfile, Encoding.UTF8)
'other logic
readerWriterLock.ExitWriteLock()
'anything reading from this would need to have the following
readerWriterLock.EnterReadLock()
'logic
readerWriterLock.ExitReadLock()
Try this and then if not successful post the exception message and any other information that you can.