Wait until download is complete before starting other tasks - vb.net

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

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

DownloadCompleteEvent fires twice

I have a windows form which lets the user select a file name and download it, Once the download is complete, a download finished event is triggered which unzips the downloaded file and processes it further.
The downloadComplete event seems to be triggered twice which is causing the unzipping action to happen twice. Is there a reason why the event would be fired twice?
below is the function which triggers the download and Handles the download complete event.
Private Async Sub Btn_download_Click(sender As Object, e As EventArgs) Handles Btn_download.Click
Dim fileNameRows As DataGridViewSelectedRowCollection = datagridview_cloudContent.SelectedRows
Dim fileName As String
Dim fileType As String = AWSGlobals.CONTENT
For Each fileNameRow As DataGridViewRow In fileNameRows
fileName = fileNameRow.Cells(0).Value.ToString() & ".zip"
Try
Await DownloadAsyncFile(fileName, fileType)
Catch ex As Exception
CSMessageBox.ShowError("Content Import failed : ", ex)
End Try
Next
End Sub
Private Function UnzipAndImport(filename As String) Handles s3obj.DownloadDone
If Not System.IO.Directory.Exists(AWSGlobals.ContentPath & Path.GetFileNameWithoutExtension(filename)) Then
Dim zipFilePath As String = DiskPath & filename
Dim zipFileArchive As ZipArchive = ZipFile.OpenRead(zipFilePath)
Dim FullPathOfContent As String = DiskPath
ExtractToDirectoryOverWrite(zipFileArchive, FullPathOfContent, True)
ImportExport.BatchImportContent(New ArrayList(New String() {ExtractedPath}), objmanager)
End If
End Function
In the class that handles the downloads, the download method is implemented as below :
Public Function DownloadAsyncFile(FileName As String, FileType As String)
Try
Dim Address As String
AddHandler fileReader.DownloadFileCompleted, Sub(sender, e) download_complete(FileName)
AddHandler fileReader.DownloadProgressChanged, AddressOf Download_ProgressChanged
fileReader.DownloadFileAsync(New Uri(Address), AWSGlobals.ContentPath + FileName)
Catch ex As Exception
MsgBox( ex.Message)
End Try
End If
End Function
Private Sub download_complete(filename As String)
RaiseEvent DownloadDone(filename)
End Sub
Can someone tell me if this implementation causes the download complete event to fire twice when just one file is being downloaded?

My consol app close after start but its works normaly if i start the normal way

I simpily made a consol app which is a TCP-server if i start the normal way like go to the .exe and start with click it works normaly soo not that is the problem as i think. What i want to do is just read the last line from that consol Here is my code
I got this code from another website
Sub Go()
Dim p As New Process
p.StartInfo.FileName = mainloc & "\ut\server.exe"
p.StartInfo.UseShellExecute = False
p.StartInfo.RedirectStandardOutput = True
AddHandler p.OutputDataReceived, AddressOf HelloMum
MsgBox(p.StartInfo.FileName.ToString)
p.Start()
p.BeginOutputReadLine()
End Sub
Sub HelloMum(ByVal sender As Object, ByVal e As DataReceivedEventArgs)
UpdateTextBox(e.Data)
End Sub
Private Delegate Sub UpdateTextBoxDelegate(ByVal Text As String)
Private Sub UpdateTextBox(ByVal Tex As String)
If Me.InvokeRequired Then
Dim del As New UpdateTextBoxDelegate(AddressOf UpdateTextBox)
Dim args As Object() = {Tex}
Me.Invoke(del, args)
Else
RichTextBox1.Text &= Tex & Environment.NewLine
End If
End Sub
And my problem is when i start this code with the Go sub the consol app show up for a second then it close by that second before it close it read the first two line then close, Before you ask in the consol app there is this line Console.ReadLine()
I am totally have no idea what i can do.
Thanks for any help

Cannot update textbox vb.net -crossthreading

I am trying to make an application to run multiple (adb specially) commands and get the output and display in a label.
First of all, i need to start the process to execute the commands. Thanks to stackoverflow and #pasty I found this (second reply): How to get Output of a Command Prompt Window line by line in Visual Basic?
Well, i thought that because it outputted to the console, it would be simple to just write it to the label. BIG MISTAKE! It gives me a cross threading error!
A little bit of googling and stack overflow I found this: vb.net accessed from a thread other than the thread it was created on
Well, i tried to implement that, but the program just crashes freezes.
Here is the code:
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
' store error output lines
Dim executable As String() = {"adb", "adb"}
Dim arguments As String() = {"help", "reboot"}
For i As Integer = 0 To 0
Dim process = New Process()
process.StartInfo = createStartInfo(executable(i), arguments(i))
process.EnableRaisingEvents = True
AddHandler process.Exited, Sub(ByVal sendera As Object, ByVal ea As System.EventArgs)
Console.WriteLine(process.ExitTime)
Console.WriteLine(". Processing done.")
'UpdateTextBox(ea)
End Sub
' catch standard output
AddHandler process.OutputDataReceived, Sub(ByVal senderb As Object, ByVal eb As DataReceivedEventArgs)
If (Not String.IsNullOrEmpty(eb.Data)) Then
Console.WriteLine(String.Format("{0}> {1}", DateTime.Now.ToString("dd.MM.yyyy HH:mm:ss"), eb.Data))
'UpdateTextBox(eb.Data)
End If
End Sub
' catch errors
AddHandler process.ErrorDataReceived, Sub(ByVal senderc As Object, ByVal ec As DataReceivedEventArgs)
Console.WriteLine(String.Format("! {0}", ec.Data))
Dim a As String = String.Format("! {0}", ec.Data)
'UpdateTextBox(a)
End Sub
' start process
Dim result = process.Start()
' and wait for output
process.BeginOutputReadLine()
' and wait for errors :-)
process.BeginErrorReadLine()
process.WaitForExit()
Next
End Sub
Private Sub UpdateTextBox(ByVal a As String)
If Me.InvokeRequired Then
Dim args() As String = {a}
Me.Invoke(New Action(Of String)(AddressOf UpdateTextBox), args)
Return
End If
Label1.Text += "a"
End Sub
Private Function createStartInfo(ByVal executable As String, ByVal arguments As String) As ProcessStartInfo
Dim processStartInfo = New ProcessStartInfo(executable, arguments)
processStartInfo.WorkingDirectory = Path.GetDirectoryName(executable)
' we want to read standard output
processStartInfo.RedirectStandardOutput = True
' we want to read the standard error
processStartInfo.RedirectStandardError = True
processStartInfo.UseShellExecute = False
processStartInfo.ErrorDialog = False
processStartInfo.CreateNoWindow = True
Return processStartInfo
End Function
And the source code: https://github.com/iAmAOpenSource/SyncfusionWindowsFormsApplication3
The call to process.WaitForExit() will block the UI thread until the spawned process exits, but while processing the output in the process.OutputDataReceived event you are calling Me.Invoke which tries to run the code on the UI thread, which is blocked, so the program freezes. You could move the logic in Button1_Click onto another thread, e.g.
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Task.Run(
Sub()
... current logic
End Sub
)
End Sub
That way the UI thread won't be blocked and the Me.Invoke call won't cause a deadlock.

Reading Output from cmd asynchronously

I'm using this code:
Shared sb_OutputData As New StringBuilder()
Shared sb_ErrorData As New StringBuilder()
Shared proc As Process = Nothing
Private Sub cmd()
If proc IsNot Nothing Then
Exit Sub
End If
Dim info As New ProcessStartInfo("cmd.exe")
' Redirect the standard output of the process.
info.RedirectStandardOutput = True
info.RedirectStandardInput = True
info.RedirectStandardError = True
' Set UseShellExecute to false for redirection
info.UseShellExecute = False
proc = New Process
proc.StartInfo = info
' Set our event handler to asynchronously read the sort output.
AddHandler proc.OutputDataReceived, AddressOf proc_OutputDataReceived
AddHandler proc.ErrorDataReceived, AddressOf proc_ErrorDataReceived
proc.Start()
' Start the asynchronous read of the sort output stream. Note this line!
proc.BeginOutputReadLine()
proc.BeginErrorReadLine()
End Sub
Private Shared Sub proc_ErrorDataReceived(sender As Object, e As DataReceivedEventArgs)
'Console.WriteLine("Error data: {0}", e.Data)
sb_ErrorData.AppendLine(e.Data)
End Sub
Private Shared Sub proc_OutputDataReceived(sender As Object, e As DataReceivedEventArgs)
' Console.WriteLine("Output data: {0}", e.Data)
sb_OutputData.AppendLine(e.Data)
End Sub
Sub CmdWrite(arguments As String)
Dim writeStream As StreamWriter = proc.StandardInput
writeStream.WriteLine(arguments)
End Sub
It works exactly as I want, be able to retrieve cmd output and error data without closing it (and asynchronously), however, I'm not able to know when the command is finished executing. I'd like to know when it reaches the end of the stream for me to grab all the output and do something with it...
I've been searching for quite long, and can't find an answer to this.
Help please?
The stream is open is long as the command window is open. You can't tell when they stop or start. If the command you're running doesn't indicate its end with a unique/detectable pattern, you're stuck, unless you can edit the script you're running and insert a string - which you can't guarantee won't show in the normal output - in between the command calls.