Making my Multi-threaded Task Asynchronous VB.net - vb.net

So I have a task which loops through a sub as many times as there are people in my array. I know that making this Async will be a lot better, but I am not sure the best way to go about doing this. Here is my code:
Private Sub status()
Dim selectedStreamers As New List(Of String)
For Each item In streamerList.CheckedItems
selectedStreamers.Add(item)
Next
Dim tasks As New List(Of Task)()
For Each streamerName In selectedStreamers
Dim t As Task = Task.Run(Sub()
streamerOnline(streamerName)
Select Case condition
Case StreamersCondition.Streaming
Me.Invoke(Sub() console.Text &= streamerName + " is online." & vbNewLine)
Case StreamersCondition.Offline
Me.Invoke(Sub() console.Text &= streamerName + " is offline." & vbNewLine)
Case StreamersCondition.UserNotFound
Me.Invoke(Sub() console.Text &= streamerName + " hasn't been found." & vbNewLine)
End Select
End Sub)
tasks.Add(t)
Next
Task.WaitAll(tasks.ToArray())
End Sub
Also, nothing related to the question at hand, but if I wanted to constantly check that the streamers were streaming. I would set up a timer, right? Except I can't seem to get this working whilst running this sub? So is there any good way to keep constantly checking (so running this sub) twice per minute?

Related

I need to beep when I add a listbox item within my thread. It's in a thread and the thread subroutine will not let me do anything, but add the item

I would like to add some data to a listbox and then play a sound (Beep). I tried the following code:
Public Sub serverThread()
'
'Listen for data.
'
Dim udpclient As New UdpClient(8080)
'
While Me.Visible
Dim remoteipendpoint As New IPEndPoint(IPAddress.Any, 0)
Dim receivebytes As Byte()
'
receivebytes = udpclient.Receive(remoteipendpoint)
returndata = Encoding.ASCII.GetString(receivebytes)
'
ListBox1.Items.Add(remoteipendpoint.Address.ToString() + ":" + returndata.ToString())
Beep()
End While
'
End Sub
When execution gets to beep, it throws an exception saying something about cross threading and I can't do this while in the thread. I thought while I was in the thread subroutine I could do anything. Can you help?
If you want to go the "simplest" route, then just use Invoke() with an anonymous method whenever you want to update the GUI:
Dim entry As String = remoteipendpoint.Address.ToString() + ":" + returndata.ToString()
Me.Invoke(Sub()
ListBox1.Items.Add(entry)
Beep()
End Sub)

how to use delegate in multi thread loop

I am trying to populate a combobox with elements quickly. If I iterate through them, the delay is too long for my liking (a few seconds). So after researching options, I'm trying to use a parallel loop to do it async. However, I am getting a cross threading error populating the combo box, so I am trying to use a delegate sub, but vb won't let me saying that "add_item is a type and cannot be used as an expression." My code is below:
Parallel.For(0, search_results.Count - 1, Sub(x)
add_item(search_box, search_results(x)(0) & " - " & search_results(x)(1) & " " & search_results(x)(2))
End Sub)
Private Delegate Sub add_item(ByVal sb As ComboBox, ByVal txt As String)
Private Sub update_search_box(ByVal sb As ComboBox, ByVal txt As String)
If sb.InvokeRequired Then
sb.Invoke(New add_item(AddressOf update_search_box), New Object() {sb, txt})
Else
sb.Items.Add(txt)
End If
End Sub
add_item is a delegate which you use to refer to your original update_search_box method.
You already use the delegate from within the update_search_box method itself, so all you need to do is to call the actual method:
Parallel.For(0, search_results.Count - 1, Sub(x)
update_search_box(search_box, search_results(x)(0) & " - " & search_results(x)(1) & " " & search_results(x)(2))
End Sub)

Multiple timers, same handler? MultiThreading, Server requests

I am trying to make a program that will test a small server.
The task is to make cyclic readings at some intervals.
For this, I thought to create separate timers (each one with its own interval), but on the Tick event, to have same handler.
Some code:
If testStarted Then
Dim timer As New CustomTimer(tempCyclicReading.ReadInterval)
timer.AssignedNodeId = tempCyclicReading.NodeId
timer.DisplayName = tempCyclicReading.DisplayName
AddHandler timer.Elapsed, AddressOf CyclicReadingTimer_Tick
timer.Enabled = True
cyclicReadingTimers.Add(timer)
End If
Private Sub CyclicReadingTimer_Tick(sender As Object, e As EventArgs)
Try
Dim obj As CustomTimer = TryCast(sender, CustomTimer)
Dim info As DataValue = ServerObj.ReadValue(tEO.Session, obj.AssignedNodeId)
Dim temp As String = ""
temp &= " Cyclic reading: "
temp &= "ItemName: " & obj.DisplayName & ", "
temp &= "Value: " & CStr(info.Value) & ", "
LogInBox(rtbLiveData, Color.ForestGreen, temp)
Catch ex As Exception
Utils.ReadException(ex)
logger.Error("Error when ticking cycling reading timer!" & vbCrLf & Utils.ReadException(ex))
End Try
End Sub
Now I have some questions:
Is it ok to have same handler for all timers? What happens if 2 or timers will call the function ServerObj.ReadValue (which connects to the server and read a value from a node) ? I know that each timer is created in each own thread. BUt when the tick event happens, and the handler function is called, is a new instance (corresponding to each timer) of the handler, created ?
DO I need to provide a lock mechanism or the server will handle this itself?
I also have a LogInBox function, that writes some results in a richtexbox. Again, does the richtextBox have a buffer or a queue and knows to prioritize and server each call to display data?
Thank you so much!

VB.NET Constantly polling a directory for files is causing an ongoing increase in memory usage

I have a small application that on startup instantiates a class, reads in a JSON array from a text file and builds an object. It then initialises a new system.timers.timer and scans a directory every xx minute(s) to get a file count.
When my application starts up, Windows Task Manager shows it at around 13MB of Memory, however, leaving it to run for about 12 hours now shows it at 700+MB of Memory when no files were even detected in this directory. I'm worried that this is just going to grow forever and eventually crash. It doesn't seem like anything is getting cleaned up by the GC (probably because my timer and objects are always active).
Although this class is quite simple in theory, I do have other functions and timers involved that don't get called until required. I am happy to post all my code somewhere, or even just the class, but it's a little too long to add to this post.
I have included the main elements of my class however:
Public Sub Initialize()
Try
If AgentSettings.DebugMode Then CreateLog("Entered Module: Initialize()")
CreateLog("Initializing Alerting Rule: <" & Me.Name & ">")
CreateLog("Validating all Rule Profiles")
'Loop through all the Rule Profiles and make sure they are all valid
'If not, disable the profile so it never scans
For Each profile As RuleProfile In Me.RuleProfiles
CreateLog("Verifying Profile: <" & profile.Name & ">")
'Check to make sure the Profile is functionally valid
If ProfileIsValid(profile) Then
'Profile is valid so setup an event handler to handle the isBroken event
profile.Active = True
CreateLog("Rule Profile: <" & profile.Name & "> Enabled")
AddHandler profile.BrokenStatusChanged, AddressOf CheckIfRuleIsBroken
Else
'Disable the Profile
CreateLog("Rule Profile: <" & profile.Name & "> Disabled")
profile.Active = False
End If
Next
'Setup the checking timer and set the interval to the default checking time
If Me.ruleCheckTimer Is Nothing Then
Me.ruleCheckTimer = New System.Timers.Timer
Me.ruleCheckTimer.AutoReset = True
Me.ruleCheckTimer.Interval = Me.DefaultMinutesToCheck * 60000
AddHandler Me.ruleCheckTimer.Elapsed, AddressOf CheckRule
End If
'If there is an escalation plan then setup a timer for it
If Me.EscalationPlan.PlanItems.Count > 0 Then
'Setup the escalation timer and set the interval to the first plan item
If Me.escalationTimer Is Nothing Then
Me.escalationTimer = New System.Timers.Timer
Me.escalationTimer.AutoReset = True
AddHandler Me.escalationTimer.Elapsed, AddressOf Escalate
End If
End If
Catch ex As Exception
CreateLog("Module: Initialize()" & vbNewLine & "Exception Error: " & ex.Message)
End Try
End Sub
Private Sub CheckRule(ByVal sender As Object, ByVal e As EventArgs)
Try
If AgentSettings.DebugMode Then CreateLog("Entered Module: CheckRule()")
'Loop through all the profiles
For Each profile As RuleProfile In Me.RuleProfiles
CreateLog("Scanning for active profiles")
'Make sure the Profile is active
If profile.Active Then
Select Case profile.Name
Case "FileCount"
CreateLog("Checking Rule Profile: <" & profile.Name & "> for isBroken status")
'Get file count of the specified directory
Dim files As String()
files = Directory.GetFiles(profile.DirectoryPath)
'Determine if we're checking for files greater than or less than
If profile.GreaterThan Then
If files.Count > profile.FileCount Then
profile.isBroken = True
Else
profile.isBroken = False
End If
Else
If files.Count < profile.FileCount Then
profile.isBroken = True
Else
profile.isBroken = False
End If
End If
files = Nothing
End Select
End If
Next
Catch ex As Exception
CreateLog("Module: CheckRule()" & vbNewLine & "Exception Error: " & ex.Message)
End Try
End Sub
When profile.isBroken receives an update, it triggers an event which then loops through all profiles to determine which ones are broken. If all are broken, then it begins its alerting and escalation process. However, none of this is occurring and my memory is still increasing constantly.
Can anyone see anything wrong with this? Or maybe a better way (and more efficient way) of polling this directory?
The theory behind my application is to instantiate multiple classes to allow various directories or files to be scanned, but if the memory has increased this much with just the one instance, I'm concerned about running multiple.
EDITED
My log handling is as follows:
Public Sub CreateLog(ByVal text As String)
Try
'Create Log Directory if it doesn't exist
If (Not System.IO.Directory.Exists(GetFolderPath(SpecialFolder.CommonApplicationData) & "\MyCompany\Agent\Log")) Then System.IO.Directory.CreateDirectory(GetFolderPath(SpecialFolder.CommonApplicationData) & "\MyCompany\Agent\Log\")
'Write log entry
LogFile = GetFolderPath(Environment.SpecialFolder.CommonApplicationData) & "\MyCompany\Agent\Log\" & "Logfile - " & Format(Now, "ddMMyyyy") & ".txt"
File.AppendAllText(LogFile, DateTime.Now.ToString("dd/MM/yyyy HH:mm:ss") & " " & text & Environment.NewLine)
'Update the activity text on the main UI
frmMain.UpdateActivityText(text)
Catch ex As Exception
'No need to create a log entry
End Try
End Sub

Control.BeginInvoke not working with TextBox.AppendText, Cross Threading

I want to expose a method to update a text box with messages as a status log. I would like to use the AppendText method but I'm experiencing a strange multi threading issue when using it. I'm able to append new messages by concatenation just fine. The issue presents it's self as the text box not being shown, and then the Cross Thread access error shows up when closing the form. Here are examples of what works and what does not:
Working but not like AppendText unless extra steps are taken which is the last resort:
Public Sub AddMessage2(ByVal newMessage As String)
If TextBoxStatus.InvokeRequired Then
TextBoxStatus.BeginInvoke(Sub() TextBoxStatus.Text = TextBoxStatus.Text & newMessage & ControlChars.CrLf)
Else
TextBoxStatus.Text = TextBoxStatus.Text & newMessage & ControlChars.CrLf
End If
End Sub
What I would like to use but does not work:
Public Sub AddMessage(ByVal newMessage As String)
If TextBoxStatus.InvokeRequired Then
TextBoxStatus.BeginInvoke(Sub() TextBoxStatus.AppendText(newMessage))
Else
TextBoxStatus.AppendText(newMessage)
End If
End Sub
Additional info and updates:
First of all I apologize for likely not supplying enough info up front.
At least part of the issue seems to be instantiating the form and calling AddMessage(newMessage) before calling Show() because the following code works:
Public Sub AddMessage(ByVal newMessage As String)
If Me.Created Then
If TextBoxStatus.InvokeRequired Then
TextBoxStatus.BeginInvoke(Sub() TextBoxStatus.AppendText(newMessage))
Else
TextBoxStatus.AppendText(newMessage)
End If
End If
End Sub
I can always do something like the following, but I would like to what's going on :)
Private backLog As String = ""
Public Sub AddMessage(ByVal newMessage As String)
If Me.Created Then
If TextBoxStatus.InvokeRequired Then
TextBoxStatus.BeginInvoke(Sub() TextBoxStatus.AppendText(backLog & newMessage))
Else
TextBoxStatus.AppendText(backLog & newMessage)
End If
backLog = ""
Else
backLog &= newMessage
End If
End Sub
Also I don't consider this to be very elegant... especially when I add log size limits
I would suggest this:
Public Sub AddMessage(ByVal newMessage As String)
If TextBoxStatus.InvokeRequired Then
TextBoxStatus.BeginInvoke(New Action(Of String)(AddressOf AddMessage), newMessage)
Else
TextBoxStatus.AppendText(newMessage)
End If
End Sub