How to properly exit from canceled BackgroundWorker async? - vb.net

I'm currently using this code, (which is working) but I'm not satisfied with how it looks... Is there a more professional way to do it ?
Here's the code I use now :
Private Sub BackgroundWorker_DoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker.DoWork
Try
If BackgroundWorker.CancellationPending Then
e.Cancel = True
Exit Sub
End If
LatestVersion = Web.DownloadString(UpdateLink)
If BackgroundWorker.CancellationPending Then
e.Cancel = True
Exit Sub
End If
If LatestVersion.Contains(InstalledVersion) Then
e.Result = UP_TO_DATE
Else
e.Result = OUTDATED
End If
Web.Dispose()
Catch ex As Exception
e.Result = ex.Message
End Try
End Sub
As you can see, I'm repeating two times the same condition. But imagine if there was more code, I should have repeat it more times...
My problem is that I would like to exit the Sub at anytime, as long as the BackgroundWorker.CancellationPending property is set to True.
I'm using the same condition two times because I wanna check if the operation has been canceled before, and after downloading of my string (I don't wanna wait for the string to be downloaded whereas I've already canceled the operation... it's a waste of time).
Should I use a While statement ? If yes, how ?

Don't use BackgroundWorker and this problem goes away. The code probably should be (mixture of VB and C#):
//Instance field:
CancellationTokenSource cts = ...;
//Update method
var downloadTask = HttpClient().DownloadString(UpdateLink, cts.Token);
await Task.WhenAll(downloadTask, cts.Token); //Wait with timeout
LatestVersion = await downloadTask;
If LatestVersion.Contains(InstalledVersion) Then
ShowResult(UP_TO_DATE);
Else
ShowResult(OUTDATED);
End If
And if you want to cancel, signal cts.
Also, error handling is missing. This is easy to test for and add.

I've found another solution. By the way, I've renamed Web by WebClient...
Here's my code :
Private Sub Form1_Load() Handles Form1.Load
AddHandler WebClient.DownloadStringCompleted, AddressOf WebClient_DownloadStringCompleted
End Sub
Private Sub BackgroundWorker_DoWork() Handles BackgroundWorker.DoWork
WebClient.DownloadStringAsync(New Uri(UpdateLink))
End Sub
Private Sub WebClient_DownloadStringCompleted(sender As Object, e As System.Net.DownloadStringCompletedEventArgs)
Dim Output As String = Nothing
If Not e.Cancelled Then
LatestVersion = e.Result
If LatestVersion.Contains(InstalledVersion) Then
Output = UP_TO_DATE
Else
Output = OUTDATED
End If
End If
End Sub
And, to cancel the operation, I run WebClient.CancelAsync().

Related

Cleanly stopping a thread in VB.net to avoid double error handling

I’ve got this issue with stopping a thread cleanly. I’ve tried to simplify it into a more basic version of the code below and I’m wondering if my approach is completely wrong here.
I have Form1 with a bunch of UI elements which need updating as BackgroundCode runs (I run it here so it’s a separate thread and it doesn’t hold up the UI) I then update the UI by invoking a sub
(Me.Invoke(Sub()
something.property=something
End Sub))
I’m also trying to handle some errors handed to the application by an external file. I’ve used a timer to check for the file and if it exists I grab the contents and pass it to my ErrorHandler. This Writes the Error out to a log file, displays it on screen and then aborts the background worker so that the program doesn’t continue to run. The trouble I’m getting is that by executing BackgroundThread.Abort() that action itself is triggering the ErrorHandler. Is there a way to ask the BackgroundThread to stop cleanly? I want BackgroundThread to trigger the ErrorHandler if something else goes wrong in that code.
I’m wondering about using a global boolean like “ErrorIsRunning” to restrict the ErrorHandler sub so that it can only ever run once, but this is starting to feel more and more hacky and I’m wondering if I’ve gone completely off track here and if there might be a better way to approach the entire thing.
Public Class Form1
Dim BackgroundThread As New Thread(AddressOf BackgroundCode)
Public Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
‘Hide Error Page
ErrorPage.Visible = False
ErrorLabel.Visible = False
‘Start Background Code
BackgroundThread.Start()
End Sub
Private Sub BackgroundCode()
Try
‘<Background code which runs over a number of minutes>
Catch.ex as Exception
ErrorHandler(“Error with BackgroundCode: “ + ex.Message)
End Try
End Sub
Private Sub Timer_Tick(sender As Object, e As EventArgs) Handles Timer.Tick
Dim ErrorFile As String = “C:\MyErrorFile.Err”
Dim ErrorContents As String
If File.Exists(ErrorFile) Then
Timer.Enabled = False
ErrorContents = File.ReadAllText(ErrorFile).Trim()
ErrorHandler(ErrorContents)
End If
End Sub
Public Sub ErrorHandler(ErrorText As String)
WriteLog(“ERROR” + ErrorText)
Me.Invoke(Sub()
Me.ErrorPage.Visible = True
Me.ErrorLabel.Text = ErrorText
End Sub)
BackgroundThread.Abort()
End Sub
End Class
Never abort threads.
This uses a Task and a ManualResetEvent. Without seeing the code inside of the background task it is hard to know how many stop checks might be needed.
Public Class Form1
Private BackgroundTask As Task
Private BackgroundTaskRunning As New Threading.ManualResetEvent(True)
Public Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
'Hide Error Page
ErrorPage.Visible = False
ErrorLabel.Visible = False
'Start Background Code
BackgroundTask = Task.Run(Sub() BackgroundCode())
End Sub
Private Sub BackgroundCode()
Try
'<Background code which runs over a number of minutes>
'put stop checks periodically
' e.g.
If Not BackgroundTaskRunning.WaitOne(0) Then Exit Sub 'stop check
Catch ex As Exception
ErrorHandler("Error with BackgroundCode: " + ex.Message)
End Try
End Sub
Private Sub Timer_Tick(sender As Object, e As EventArgs) Handles Timer.Tick
Dim ErrorFile As String = "C:\MyErrorFile.Err"
Dim ErrorContents As String
If File.Exists(ErrorFile) Then
Timer.Enabled = False
ErrorContents = File.ReadAllText(ErrorFile).Trim()
ErrorHandler(ErrorContents)
End If
End Sub
Public Sub ErrorHandler(ErrorText As String)
WriteLog("ERROR" + ErrorText)
Me.Invoke(Sub()
Me.ErrorPage.Visible = True
Me.ErrorLabel.Text = ErrorText
End Sub)
BackgroundTaskRunning.Reset() 'stop task <<<<<<<<<<<<<<<<<<<<<<<<<<<
End Sub
End Class

Can't get checking if file exists using textboxes to work

I've been racking my brain for days over this code and I just can't seem to get it working. I've researched and researched with no luck. I have four textboxes on my form. Two textboxes is a folder location and the other two textboxes are file locations. I'm trying to use a function that will return true or false telling if the files in the two textboxes exist or not. I don't see anything at all wrong with this code and it just won't work! I'm sure it's something simple I'm overlooking. Maybe someone else can spot it!
Private Function doesFileExist(folderPath, fileName) As Boolean
If IO.File.Exists(folderPath & "\" & fileName) Then
Return True
Else
Return False
End If
End Function
Private Sub chkStart_CheckedChanged(sender As Object, e As EventArgs) Handles chkStart.CheckedChanged
If doesFileExist(txtCPU.Text, txtFileCPU.Text) And
doesFileExist(txtGPU.Text, txtFileGPU.Text) Then
If chkStart.Checked Then
chkStart.Text = "Stop Monitor"
Else
chkStart.Checked = False
chkStart.Text = "Start Monitor"
End If
Else
chkStart.Checked = False
MessageBox.Show("Please check directory & file locations!", "Error!", MessageBoxButtons.OK)
End if
End Sub
I want to mention that before I tried nested if statements on this I also tried to separate them both like so..
Private Sub chkStart_CheckedChanged(sender As Object, e As EventArgs) Handles chkStart.CheckedChanged
If Not doesFileExist(txtCPU.Text, txtFileCPU.Text) And
Not doesFileExist(txtGPU.Text, txtFileGPU.Text) Then
chkStart.Checked = False
MessageBox.Show("Please check directory & file locations!", "Error!", MessageBoxButtons.OK)
Exit Sub
End If
If chkStart.Checked Then
chkStart.Text = "Stop Monitor"
Else
chkStart.Checked = False
chkStart.Text = "Start Monitor"
End If
End Sub
Both of these ways will show the messagebox if the application is ran with the checkbox checked on start up. Not only will it show the messagebox it also shows the messagebox twice! I've yet to figure that one out!
Your check file exists can be simplified... (It's been a while since I used VB so apologies for any syntax errors, I don't have an IDE to hand)
Function DoesFileExist(Folder as String, Filename As String) As Boolean
Return IO.File.Exists(IO.Path.Combine(Folder, Filename))
End Function
Re: Changing whether the "check" checkbox is set shouldn't perform the check itself - otherwise you only check when people click. (Incidentally, I'm guessing you're getting a message twice as code elsewhere ticks/unticks this checkbox, but it's only a guess).
Private Sub chkStart_CheckedChanged(sender As Object, e As EventArgs) Handles chkStart.CheckedChanged
If chkStart.Checked Then
chkStart.Text = "Stop Monitor"
PollTimer.Start()
Else
chkStart.Text = "Start Monitor"
PollTimer.Stop()
End if
End Sub
Finally... You need to define when your check will happen. Ideally, you'd want to use a FileSystemWatcher which will give you events when the file system changes, but you can also poll using a timer...
Private PollTimer As System.Timers.Timer
Then in your Form Main, do some initial timer setup...
...
PollTimer = New System.Timers.Timer()
PollTimer.Interval = 30000 ' Seconds
AddHandler PollTimer.Elapsed, AddressOf CheckExistsNow
PollTimer.Start()
...
And finally the code to run every time we want to make the check....
Sub CheckExistsNow(sender As Object, e As System.Timers.ElapsedEventArgs)
If Not DoesFileExist(txtGPU.Text, txtFileGPU.Text) Then
' Handle the missing file.
End if
End Sub

Wait until download is complete before starting other tasks

I am trying to download a file, and then run some adb commands. However when downloading, i could not get it to update the progress bar using
downloadFile(url,filename)` command.
A bit of searching it said that the command was blocking the UI thread, so i decided to use Task.Run() (a solution to my previous post to run ADB Commands when it blocked the UIThread).
This made no difference. Another solution that i found is to use
downloadFileAsync(url, filename)
The progress bar is updating!
But, the ADB commands are running before the file is downloaded! They are declared after, but they are still being run before the file is downloaded, which I don't want.
Here is the code:
Private Sub btnFlashRecovery_Click(sender As Object, e As EventArgs) Handles btnFlashRecovery.Click
'Insert ommited Code here (removed to simplify question)
'id is variable obtained from a previous code that was ommited here
Dim fileName As String = "downloads/twrp-" & id & ".img"
DownloadFile(url, fileName)
'run the right commands
LabelToOutput = txtBoxRecovery
Dim commands(3, 3) As String
commands = {{"adb", "reboot bootloader", "Rebooting to bootloader"},
{"fastboot", "flash recovery" & "downloads/twrp-3.1.1-0.img", "Flashing recovery: (make sure device is plugged, otherwise it will not output anything)"},
{"fastboot", "reboot", "Rebooting device"}
}
'Task to run after
Task.Run(Sub() runComands(commands))
End Sub
Private Sub UpdateProgressBar(ByVal a As Integer)
If Me.InvokeRequired Then
Dim args() As String = {a}
Me.Invoke(New Action(Of String)(AddressOf UpdateProgressBar), args)
Return
End If
ProgressBar1.Value = CInt(a)
End Sub
Public Sub DownloadFile(urlAddress As String, location As String)
Using webClient = New WebClient()
AddHandler webClient.DownloadFileCompleted, AddressOf Completed
AddHandler webClient.DownloadProgressChanged, AddressOf ProgressChanged
Try
' Start downloading the file
webClient.DownloadFileAsync(New Uri(urlAddress), location)
Catch ex As Exception
MessageBox.Show(ex.Message)
End Try
End Using
End Sub
' The event that will fire whenever the progress of the WebClient is changed
Private Sub ProgressChanged(sender As Object, e As DownloadProgressChangedEventArgs)
'Console.WriteLine(e.ProgressPercentage)
' Update the progressbar percentage only when the value is not the same.
UpdateProgressBar(e.ProgressPercentage)
End Sub
' The event that will trigger when the WebClient is completed
Private Sub Completed(sender As Object, e As AsyncCompletedEventArgs)
If e.Cancelled = True Then
MessageBox.Show("Download has been canceled.")
Else
MessageBox.Show("Download completed!")
End If
End Sub
Do it like this:
'Need Async keyword with the method
Private Async Sub btnFlashRecovery_Click(sender As Object, e As EventArgs) Handles btnFlashRecovery.Click
'Insert ommited Code here (removed to simplify question)
'id is variable obtained from a previous code that was ommited here
Dim fileName As String = "downloads/twrp-" & id & ".img"
'You need to AWAIT the result of the task
Await Task.Run(Sub() DownloadFile(url, fileName))
'run the right commands
LabelToOutput = txtBoxRecovery
Dim commands(3, 3) As String
commands = { {"adb", "reboot bootloader", "Rebooting to bootloader"},
{"fastboot", "flash recovery" & "downloads/twrp-3.1.1-0.img", "Flashing recovery: (make sure device is plugged, otherwise it will not output anything)"},
{"fastboot", "reboot", "Rebooting device"}
}
'Task to run after
'Await here, too, to allow the UI to remain responsive
Await Task.Run(Sub() runComands(commands))
End Sub

How do I call a function on the main thread from another thread in vb

I have a timer function which is called on my windows form which works perfectly when a button on the UI is clicked. However, in my application Im connecting to a server using a TCP socket, when the server disconnects Im catching the exception thrown and Id like at this point for the timer to start (ticker) then run the reconnection.
When i reference the timer from the try catch it wont run, so I imagine its because its on the main thread?
Here is my code for the timer:
Public Delegate Sub DroppedConnectionDelegate(ByVal ReConn As Integer)
Public Sub DroppedConnection(ByVal ReConn As Integer)
Console.Write("Dropped Connection()")
Thread.Sleep(1000)
If ReConn = 1 Then
MakeConnection(My.Settings.savedIP, False)
Else
isRunning = False
Second = 0
'client.Close()
'client.Dispose()
Timer1.Interval = 1000
Timer1.Enabled = True
Timer1.Start() 'Timer starts functioning
End If
' End If
End Sub
Public Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer1.Tick
Second = Second + 1
Console.WriteLine(Second)
If Second >= 10 Then
Timer1.Stop() 'Timer stops functioning
Timer1.Enabled = False
MakeConnection(ServerAddressString, False)
End If
End Sub
Here is the sub from where Id like to call the timer on catch exception:
Public Shared Sub ReceiveCallback(ByVal ar As IAsyncResult)
Dim state As StateObject = CType(ar.AsyncState, StateObject)
Dim client As Socket = state.workSocket
Dim strReceiveBuffer As String = String.Empty
' Read data from the remote device.
Dim bytesRead As Integer
Console.WriteLine("ar to string = " & ar.ToString)
'While client.Connected
If (AsynchronousClient.connectDone.WaitOne(5000, True)) Then
Try
If client.Connected = True Then
bytesRead = client.EndReceive(ar)
Console.WriteLine("Socket Connected")
If bytesRead > 0 Then
state.sb.Clear()
' There might be more data, so store the data received so far.
state.sb.Append(Encoding.ASCII.GetString(state.buffer, 0, bytesRead))
strReceiveBuffer = state.sb.ToString()
MainForm.main_Response = state.sb.ToString()
If strReceiveBuffer.IndexOf("doses-remaining") > 0 Then
response = state.sb.ToString()
MainForm.GetLabelTextToString(response)
'receiveDone.Set()
'isReceived = True
'Return
End If
' Get the rest of the data.
client.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0, New AsyncCallback(AddressOf ReceiveCallback), state)
Else
' All the data has arrived; put it in response.
If state.sb.Length > 1 Then
response = state.sb.ToString()
End If
' Signal that all bytes have been received.
receiveDone.Set()
isReceived = True
End If
Else
MainForm.WriteLog("RecieveCallback() Error outside catch :" & Date.Today.ToString & " " & TimeOfDay)
MainForm.UpdateList("RecievecallBack error, attempting reconnect..." & client.RemoteEndPoint.ToString())
MainForm.isRunning = False
MainForm.DroppedConnection(0)
End If
Catch ex As Exception
'MessageBox.Show("ReceiveCallback Error, Check Log.")
MainForm.WriteLog("RecieveCallback() Error inside catch :" & Date.Today.ToString & " " & TimeOfDay & ex.Message)
MainForm.UpdateList("RecievecallBack error, attempting reconnect..." & client.RemoteEndPoint.ToString())
client.Shutdown(SocketShutdown.Both)
client.Close()
client.Dispose()
MainForm.isRunning = False
Dim d As DroppedConnectionDelegate = AddressOf MainForm.DroppedConnection
'MainForm.DroppedConnection(0)
d.BeginInvoke(0, Nothing, Nothing)
Exit Try
End Try
End If
' End While
End Sub 'ReceiveCallback
If I run the application and the server disconnects unexpectedly, currently it wont reconnect automatically. But it will run the timer if I click the connect button (which calls the timer anyway)
Can anyone help please?
Thanks in advance
I'm not 100% sure as I've never tried but I think that if you use a Timers.Timer instead of a Windows.Forms.Timer then what you're doing now will work. By default, a Timers.Timer will raise its Elapsed event on a secondary thread but you can assign a form or other control to its SynchronizingObject property to make it raise events on the UI thread. Calling Start from a secondary thread should be OK in that case.
If that doesn't work then you can do what you want by first marshalling a method call to the UI thread before starting the Timer. That might look like this:
Private Sub StartTimer()
If Me.InvokeRequired Then
Me.Invoke(New Action(AddressOf StartTimer))
Else
Me.Timer1.Start()
End If
End Sub
You can call that method on any thread and it will just work.
Note that setting the Enabled property and calling Start or Stop is redundant. Start already sets Enabled to True and Stop sets it to False. Do one or the other, not both. It is most appropriate to call a method when you know for a fact that you want to either start or stop the Timer. If you have a Boolean value that might be either True or False then you should use the property. Setting the property using a literal value is not wrong per se but it is not what was intended. An example of where it's appropriate to use the property is when you use the same method to both start and stop the Timer from a secondary thread:
Private Sub EnableTimer(enable As Boolean)
If Me.InvokeRequired Then
Me.Invoke(New Action(Of Boolean)(AddressOf EnableTimer), enable)
Else
Me.Timer1.Enabled = enable
End If
End Sub
Again, calling that method on any thread will just work.

Constantly monitor if a process is running

I have the following code:
Dim p() As Process
Private Sub CheckIfRunning()
p = Process.GetProcessesByName("skype") 'Process name without the .exe
If p.Count > 0 Then
' Process is running
MessageBox.Show("Yes, Skype is running")
Else
' Process is not running
MessageBox.Show("No, Skype isn't running")
End If
End Sub
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
CheckIfRunning()
End Sub
And it works GREAT!
But I'm wondering how I would convert this to a monitoring application, to constantly check if the processes is running. Is it as simple as putting the check on a timer every 1 second, or is there a better, more efficient way to go about this.
In the end result, I'd like to have a label that says "Running", or "Not Running" based on the process, but I need something to watch the process constantly.
If you need the app running all the time, then you don't need a Timer at all. Subscribe to the Process.Exited() event to be notified when it closes. For instance, with Notepad:
Public Class Form1
Private P As Process
Private FileName As String = "C:\Windows\Notepad.exe"
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim ps() As Process = Process.GetProcessesByName(Path.GetFileNameWithoutExtension(FileName))
If ps.Length = 0 Then
P = Process.Start(FileName)
P.EnableRaisingEvents = True
AddHandler P.Exited, AddressOf P_Exited
Else
P = ps(0)
P.EnableRaisingEvents = True
AddHandler P.Exited, AddressOf P_Exited
End If
End Sub
Private Sub P_Exited(sender As Object, e As EventArgs)
Console.WriteLine("App Exited # " & DateTime.Now)
Console.WriteLine("Restarting app: " & FileName)
P = Process.Start(FileName)
P.EnableRaisingEvents = True
AddHandler P.Exited, AddressOf P_Exited
End Sub
End Class
That would keep it open all the time, assuming you wanted to open it if it wasn't already running.
If you don't want to open it yourself, and need to detect when it does open, then you could use WMI via the ManagementEventWatcher as in this previous SO question.
I've done something similar to this to monitor an exe that I need to be running all the time, and to restart it if it was down.
Mine was running as a Windows Service - that way it would start when windows booted and id never need to look after it.
Alternatively you could just create it as a console app and put it in your startup folder?
I had:
Sub Main()
Do
Check_server()
Dim t As New TimeSpan(0, 15, 0)
Threading.Thread.Sleep(t)
Loop
End Sub
Public Sub Check_server()
Dim current_pros() As Process = get_pros()
Dim found As Boolean = False
If Now.Hour < "22" Then
For Each pro In current_pros
If pro.ProcessName.ToLower = "Lorraine" Then
found = True
Exit For
Else
found = False
End If
Next
If found Then
Console.WriteLine("Server up")
Else
Console.WriteLine("Server down - restarting")
restart_server()
End If
End If
End Sub
My "server" app was called Lorraine...Also a timer maybe better practice than having the thread sleep..
From my experience, a simple timer works best:
'Timer interval set to 1-5 seconds... no remotely significant CPU hit
Private Sub timerTest_Tick(sender As System.Object, e As System.EventArgs) Handles timerTest.Tick
Dim p() As Process = Process.GetProcessesByName("Skype")
lblStatus.Text = If(p.Length > 0, "Skype is running.", "Skype isn't running.")
End Sub
Your mileage may vary, but I don't like to deal with separate threads unless necessary.