Stringbuilder used in Parallel.Foreach loop - .net-4.0

In my code, there is a foreach loop that iterates through all items in a collection and appends text inside a StringBuilder to create a ".ics" file. To make this faster I converted foreach loop to Parallel.Foreach loop. Though the performance was improved, the text in ".ics" file was unordered which created a corrupted file.
Example of expected text in ".ics" file =>
Begin:calendar
Begin:event1
.
.
End:event1
Begin:event2
.
.
End:event2
End:Calendar
Example of ".ics" file created by using parallel.foreach loop =>
Begin:calendar
Begin:event1
Begin:event2
.
.
.
.
End:Event1
End:Event2
Is there a way to create a unique StringBuilder for each item in collection and then join them at the end of loop to create an ordered string?

I added a new StringBuilder variable inside "Parallel.Foreach" loop and at the end of the loop the contents of new StringBuilder variable is appended with the main Stringbuilder variable.
By creating a new Stringbuilder variable inside the loop, it acts as a private object for each thread running in parallel.

Related

Streamwriter: write two listboxes on the same row

I am trying to write, in order to export on txt file, information in two listbox with the same number of rows. I have to export them with the following format: Listbox1, Listbox2. In order to do this, I've tried to use the following code:
Using writer = New StreamWriter(SaveFileDialog1.FileName)
For Each o As Object In Form3.ListBox1.Items And Form3.ListBox2.Items
writer.WriteLine(o)
Next
End Using
I'm receiving the following error:
BC30452 Operator 'And' is not defined for types 'ListBox.ObjectCollection' and 'ListBox.ObjectCollection'.
I've also tried to perform three For Each loops, the first for the LB1, the second for the commas and the third for LB2, but I'm having it exported with content on single lines. How could I solve this?
If you use Enumerable.Zip, as suggested in another answer, then you can make the code more succinct by doing away with the explicit loop:
File.WriteAllLines(SaveFileDialog1.FileName,
Form3.ListBox1.
Items.
Cast(Of Object).
Zip(Form3.ListBox2.
Items.
Cast(Of Object),
Function(x1, x2) $"{x1}, {x2}"))
If you didn't use Zip then you can use a loop this way:
Dim items1 = Form3.ListBox1.Items
Dim items2 = Form3.ListBox2.Items
Using writer = New StreamWriter(SaveFileDialog1.FileName)
For i = 0 To Math.Min(items1.Count, items2.Count)
writer.WriteLine($"{items1(i)}, {items2(i)}")
Next
End Using
The Math.Min part is just in case there are different numbers of items in each ListBox. If you know there aren't then you can do away with that and just use one Count. If there might be different counts but you want to output all items then the code would become slightly more complex to handle that.
As the error message says, the syntax you attempted is simply not valid. There's no feature in VB.NET that does that sort of thing.
However, the .NET Framework API does provide a means for something similar, which would probably work in your case. See Enumerable.Zip(). You can use it like this:
Using writer = New StreamWriter(SaveFileDialog1.FileName)
For Each o As String In Form3.ListBox1.Items.Cast(Of Object).Zip(Form3.ListBox2.Items.Cast(Of Object), Function(x1, x2) x1 & ", " & x2)
writer.WriteLine(o)
Next
End Using
Since you said that both list boxes have the same number of items we can use the number of items in the first listbox less one (indexes start at zero) in a For loop.
I used a StringBuilder so the code does not have to throw away and create a new string on each iteration.
I used an interpolated string indicate by the $ preceding the string. This means I can insert variables in braces, right along with literals.
Call .ToString on the StringBuilder to write to the text file.
Private Sub SaveListBoxes()
Dim sb As New StringBuilder
For i = 0 To ListBox1.Items.Count - 1
sb.AppendLine($"{ListBox1.Items(i)}, {ListBox2.Items(i)}")
Next
File.WriteAllText("C:\Users\xxx\Desktop\ListBoxText.txt", sb.ToString)
End Sub

RichTextBox type of output & removing duplicates

I'm trying to make a small VB program to remove duplicate lines and empty lines from plain text.
I have the RichTextBox input but eventually I do not know what the type of the returned object is, is it an array or a list maybe ?
Also I'm trying to find the most efficient way to remove duplicate lines from big plain text(s), in Python I do it this way :
lines_nodupes = {}
for elt in lines :
lines_nodupes[elt] = ""
Since you can not have twice the same key, no duplicates are kept in the lines_nodupes dictionnary and I can enumerate it to access the lines.
There are two properties you can use :
Lines will return an array of Strings
Text will return the entire text as a string
See RichTextBox from MSDN
If you want to do as in Python :
Dim noDup as new Dictionary(Of String, String)
For Each line in MyRichTextBox.Lines
if not noDup.ContainsKey(line) then
noDUp.add(line, "")
End if
Next
You can also do (as suggested by VisualVincent in the coments) :
Dim noDup as new List(Of String)
For Each line in MyRichTextBox.Lines
if not noDup.Contains(line) then
noDUp.add(line)
End if
Next
which is a bit slower but difference won't be seen unless you have a very long list of items.

VB.net delimiter "","" split strings

I'm using the below code to split a line into an array but some of the elements contain commas like the example below.
Current code : output.Add(sr.ReadLine().Split(New Char() {","c}))
Every element has "" around it in the CSV and I can't change it as it is from a supplier. I'm splitting the line into an array then removing the " using this.
'data(iRow)(0) = data(iRow)(0).Replace(Chr(34), "")
I'm running into problems because the elements like my,park that need to remain same.
Example Data : The data is read like this from the CSV/Text file
"my,park","mydate","my school","myhome"
My full function is below if that helps, been stuck on it for 2 days now.
Dim output As New ArrayList()
Using sr As StreamReader = New StreamReader(fileName)
While Not sr.EndOfStream
output.Add(sr.ReadLine().Split(New Char() {","c}))
End While
End Using
Return output
How can I complete this efficiently and get the correct output? The process is tripping up with the element containing commas and I don't know how to split on ","
Thanks in advance

VB.Net Read multi column text file and load into ListBox

First, I am not a programmer, I mainly just do simple scripts however there are somethings that are just easier to do in VB, I am pretty much self taught so forgive me if this sounds basic or if I can't explain it to well.
I have run into an issue trying to load a multi-column text file into a list box. There are two separate issues.
First issue is to read the text file and only grab the first column to use in the listbox, I am currently using ReadAllLines to copy the text file to a string first.
Dim RDPItems() As String = IO.File.ReadAllLines(MyDocsDir & "\RDPservers.txt")
However I am having a difficult time finding the correct code to only grab the first Column of this string to put in the listbox, if I use the split option I get an error that "Value of type '1-dimensional array of String' cannot be converted to 'String'"
The code looked like
frmRDP.lstRDP.Items.Add() = Split(RDPItems, ";", CompareMethod.Text)
This is the first hurdle, the second issue is what I want to do is if an item is selected from the List box, the value of the second column gets pulled into a variable to use.
This part I'm not even sure where to begin.
Example data of the text file
Server1 ; 10.1.1.1:3389
Server2 ; 192.168.1.1:8080
Server3 ; 172.16.0.1:9833
.....
When it's working the application will read a text file with a list of servers and their IPs and put the servers in a listbox, when you select the server from the listbox it and click a connect button it will then launch
c:\windows\system32\mstsc.exe /v:serverip
Any help would be appreciated, as I can hard code a large list of this into the VB application it would be easier to just have a text file with a list of servers and IPs to load instead.
The best practise for this would probably be to store your "columns" in a Dictionary. Declare this at class level (that is, outside any Sub or Function):
Dim Servers As New Dictionary(Of String, String)
When you load your items you read the file line-by-line, adding the items to the Dictionary and the ListBox at the same time:
Using Reader As New IO.StreamReader(IO.Path.Combine(MyDocsDir, "RDPservers.txt")) 'Open the file.
While Reader.EndOfStream = False 'Loop until the StreamReader has read the whole file.
Dim Line As String = Reader.ReadLine() 'Read a line.
Dim LineParts() As String = Line.Split(New String() {" ; "}, StringSplitOptions.None) 'Split the line into two parts.
Servers.Add(LineParts(0), LineParts(1)) 'Add them to the Dictionary. LineParts(0) is the name, LineParts(1) is the IP-address.
lstRDP.Items.Add(LineParts(0)) 'Add the name to the ListBox.
End While
End Using 'Dispose the StreamReader.
(Note that I used IO.Path.Combine() instead of simply concatenating the strings. I recommend using that instead for joining paths together)
Now, whenever you want to get the IP-address from the selected item you can just do for example:
Dim IP As String = Servers(lstRDP.SelectedItem.ToString())
Hope this helps!
EDIT:
Missed that you wanted to start a process with it... But it's like charliefox2 wrote:
Process.Start("c:\windows\system32\mstsc.exe", "/v:" & Servers(lstRDP.SelectedItem.ToString()))
Edit: #Visual Vincent's answer is way cleaner. I'll leave mine, but I recommend using his solution instead. That said, scroll down a little for how to open the server. He's got that too! Upvote his answer, and mark it as correct!
It looks like you're trying to split an array. Also, ListBox.Items.Add() works a bit differently than the way you've written your code. Let's take a look.
ListBox.Items.Add() requires that you provide it with a string inside the parameters. So you would do it like this:
frmRDP.lstRDP.Items.Add(Split(RDPItems, ";", CompareMethod.Text))
But don't do that!
When you call Split(), you must supply it with a string, not an array. In this case, RDPItems is an array, so we can't split the entire thing at once. This is the source of the error you were getting. Instead, we'll have to do it one item at a time. For this, we can use a For Each loop. See here for more info if you're not familiar with the concept.
A For Each loop will execute a block of code for each item in a collection. Using this, we get:
For Each item In RDPItems
Dim splitline() As String = Split(item, ";") 'splits the item by semicolon, and puts each portion into the array
frmRDP.lstRDP.Items.Add(splitline(0)) 'adds the first item in the array
Next
OK, so that gets us our server list put in our ListBox. But now, we want to open the server that our user has selected. To do that, we'll need an event handler (to know when the user has double clicked something), we'll have to find out which server they selected, and then we'll have to open that server.
We'll start by handling the double click by creating a sub to deal with it:
Private Sub lstRDP_MouseDoubleClick(sender As Object, e As MouseEventArgs) Handles lstRDP.MouseDoubleClick
Next, we'll get what the user has selected. Here, we're setting selection equal to the index that the user has selected (in this case, the first item is 0, the second is 1, and so on).
Dim selection As Integer = lstRDP.SelectedIndex
Lastly, we need to open the server. I'm assuming you want to do that in windows explorer, but if I'm mistaken please let me know.
Dim splitline() As String = Split(RDPItems(selection), ";")
Dim location As String = Trim(splitline(1))
We'll need to split the string again, but you'll notice this time I'm choosing the item whose location in the array is the same as the index of the list box the user has selected. Since we added our items to our listbox in the order they were added to our array, the first item in our listbox will be the first in the array, and so on. The location of the server will be the second part of the split function, or splitline(1). I've also included the Trim() function, which will remove any leading or trailing spaces.
Finally, we need to connect to our server. We'll use Process.Start() to launch the process.
Process.Start("c:\windows\system32\mstsc.exe", "/v:" & location)
For future reference, to first argument for Process.Start() is the location of the process, and the second argument is any argument the process might take (in this case, what to connect to).
Our final double click event handler looks something like this:
Private Sub lstRDP_MouseDoubleClick(sender As Object, e As MouseEventArgs) Handles lstRDP.MouseDoubleClick
Dim selection As Integer = lstRDP.SelectedIndex
Dim splitline() As String = Split(RDPItems(selection), ";")
Dim location As String = Trim(splitline(1))
Process.Start("c:\windows\system32\mstsc.exe", "/v:" & location)
End Sub
A final note: You may need to put
Dim RDPItems() As String = IO.File.ReadAllLines(MyDocsDir & "\RDPservers.txt")
outside of a sub, and instead just inside your class. This will ensure that both the click handler and your other sub where you populate the list box can both read from it.

Avoid updating textbox in real time in vb.net

I have a very simple code in a VB.NET program to load all paths in a folder in a text box. The code works great, the problem is that it adds the lines in real time, so it takes about 3 minutes to load 20k files while the interface is displaying line by line.
This is my code:
Dim ImageryDB As String() = IO.Directory.GetFiles("c:\myimages\")
For Each image In ImageryDB
txtbAllimg.AppendText(image & vbCrLf)
Next
How can I force my program to load the files in chunks or update the interface every second?
Thanks in advance
Yes, you can do that. You'll need to load the file names into an off-screen data structure of some kind rather than loading them directly into the control. Then you can periodically update the control to display whatever is loaded so far. However, I think you'll find that the slowness comes only from updating the control. Once you remove that part, there will be no need to update the control periodically during the loading process since it will be nearly instantaneous.
You could just load all of the file names into a string and then only set the text box to that string after it's been fully loaded, like this:
Dim imagePaths As String = ""
For Each image As String In Directory.GetFiles("c:\myimages\")
imagePaths &= image & Environment.NewLine
Next
txtbAllimg.Text = imagePaths
However, that's not as efficient as using the StringBuilder:
Dim imagePaths As New StringBuilder()
For Each image As String In Directory.GetFiles("c:\myimages\")
imagePaths.AppendLine(image)
Next
txtbAllimg.Text = imagePaths.ToString()
However, since the GetFiles method is already returning the complete list of paths to you as a string array, it would be even more convenient (and likely even more efficient) to just use the String.Join method to combine all of the items in the array into a single string:
txtbAllimg.Text = String.Join(Environment.NewLine, Directory.GetFiles("c:\myimages\"))
I know that this is not an answer to your actual question, but AppendText is slow. Using a ListBox and Adding the items to it is approx. 3 times faster. The ListBox also has the benefit of being able to select an item easily (at least more easily than a TextBox)
For each image in ImageryDB
Me.ListBox1.Items.add (image)
Next
However, there is probably an even more useful and faster way to do this. Using FileInfo.
Dim dir As New IO.DirectoryInfo("C:\myImages")
Dim fileInfoArray As IO.FileInfo() = dir.GetFiles()
Dim fileInfo As IO.FileInfo
For Each fileInfo In fileInfoArray
Me.ListBox2.Items.Add(fileInfo.Name)
Next