I am creating a program to download files from download websites. I've created a background worker to handle download large files as it usually freezes the UI when downloading large files.
I've managed to make it work but the problem I am facing now is that I am not able to use my AddHandler to show the changed progress, so I tried to use an invoke method for the progress changed values.
This is the code I tried for the invoke method:
Dim ProgressChanged As New ProgressChange(AddressOf bw_ProgressChanged)
Me.Invoke(ProgressChanged, Nothing, EventArgs.Empty)
This is my ProgressChanged handler.
Private Sub bw_ProgressChanged(ByVal sender As Object, ByVal e As DownloadProgressChangedEventArgs)
Dim bytesIn As Double = Double.Parse(e.BytesReceived.ToString())
Dim totalBytes As Double = Double.Parse(e.TotalBytesToReceive.ToString())
Dim percentage As Double = bytesIn / totalBytes * 100
ProgressBarCurrent.Value = Int32.Parse(Math.Truncate(percentage).ToString())
Dim BytesDownloaded As String = (e.BytesReceived / (DirectCast(e.UserState, Stopwatch).ElapsedMilliseconds / 1000.0#)).ToString("#")
If BytesDownloaded < 1024 Then
Dim Bs As String = Convert.ToInt32(BytesDownloaded)
Label4.Text = (Bs & " B/s")
ElseIf BytesDownloaded < 1048576 Then
Dim KBs As String = Math.Round(BytesDownloaded / 1024, 2)
Label4.Text = (KBs & " KB/s")
ElseIf BytesDownloaded < 1073741824 Then
Dim MBs As String = Math.Round(BytesDownloaded / 1048576, 2)
Label4.Text = (MBs & " MB/s")
ElseIf BytesDownloaded < 1099511627776 Then
Dim GBs As String = Math.Round(BytesDownloaded / 1073741824, 2)
Label4.Text = (GBs & " GB/s")
Else
Label4.Text = ("Estimating...")
End If
End Sub
It's got some more code but I don't think it's necessary to show.
And this is my delegate sub.
Delegate Sub ProgressChange(ByVal sender As Object, ByVal e As DownloadProgressChangedEventArgs)
I've also tried a few different things with addhandler method.
AddHandler wc.DownloadProgressChanged, AddressOf bw_ProgressChanged
Before when I used this code I was getting an error but now when I use it, there is no error but the code doesn't actually do anything, like it's not even fired, so I figured add handlers wouldn't work.
I wasn't sure if it was possible to use Invoke method for DownloadProgressChanged, but I believe it should be and I am not sure what arguments, I would use. I have tried different arguments that I thought would work but they didn't.
You need to call [YourBackgroundWorkerObject].ReportProgress from inside DoWork. This triggers the ProgressChanged event.
Your ProgressChanged-procedure must then invoke the method that does the UI changes.
(BTW, you can as well skip that Progress-Reporting-Reroute of the BGW. Invoke your own UI-changing method directly from DoWork.)
Related
I am developing a panel, which from time to time runs a process and generates an image within it. Once the image is generated, you need to take a photo to save it for the changes that are made.
I already developed the part where the images change, but when I take the photos, they all come out blank. Add a delay on the screen thinking that it should take a while to take the photo of the panel later, but it still comes out blank and if there is something inside the panel, since I am resizing it according to the size of what is updated.
Can you help me to see where my error is? Or guide me to obtain an optimal result?, I also tried using a timer, but it gives me the same result, any ideas?
This is the code developed.
sub buldimages()
Panel1.Refresh()
System.Threading.Thread.Sleep(5000)
segondGa(varName)
'Timer1.Start()
End Sub
Function segondGa(nameLine As String)
Dim widthOldPuno = Panel1.Width
Dim heightOldPuno = Panel1.Height
Dim widthOldPdos = Panel2.Width
Dim heightOldPdos = Panel2.Height
Dim coordenada, i As Integer
For Each obj As Control In Panel1.Controls
coordenada = obj.Location.X
Next
Panel1.Width = widthOldPuno + (coordenada - Panel1.Width)
Panel2.Width = Panel1.Width + 113
Panel2.Height = heightOldPdos - 65
Dim nameLineB As String = nameLine
Using bmp = New Bitmap(Panel2.Width, Panel2.Height)
Panel2.DrawToBitmap(bmp, New Rectangle(0, 0, bmp.Width, bmp.Height))
Dim bmp2 As New Bitmap(bmp.Width * 3, bmp.Height * 3)
Dim gr As Graphics = Graphics.FromImage(bmp2)
gr.DrawImage(bmp, New Rectangle(0, 0, bmp2.Width, bmp2.Height))
bmp2.Save("C:\TEMP\" & nameLineB & ".png")
End Using
Panel2.Width = widthOldPdos
Panel2.Height = heightOldPdos
Panel1.Width = widthOldPuno
End Function
Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick
Dim seconds As Integer
Label1.Text = seconds + 1
If Label1.Text = "5" Then
Timer1.Stop()
End If
End Sub
I have in my code, a file upload script that needs to post a authentication key header along with the upload. However, it doesn't seem to work like I expected. This is my code so far:
Private Sub uploadFile()
Dim address As String = "http://localhost/stripe/upload.php"
Dim client As WebClient = New WebClient()
Dim uri As Uri = New Uri(address)
AddHandler client.UploadFileCompleted, AddressOf UploadFileCompleted
AddHandler client.UploadProgressChanged, AddressOf UploadProgressCallback
client.Headers.Set("authKey", login.authKey)
client.UploadFileAsync(uri, "POST", uploadFileName)
End Sub
Private Sub UploadProgressCallback(ByVal sender As Object, ByVal e As UploadProgressChangedEventArgs)
ProgressBar1.Value = e.ProgressPercentage
Label3.Text = String.Format("{0} / {1} kB", Math.Round(e.BytesSent / 1024), Math.Round(e.TotalBytesToSend / 1024))
Label2.Text = String.Format("{0}%", e.ProgressPercentage)
Application.DoEvents()
End Sub
Private Sub UploadFileCompleted(ByVal sender As Object, ByVal e As UploadFileCompletedEventArgs)
Dim response As String = System.Text.Encoding.ASCII.GetString(e.Result)
Console.WriteLine(response)
End Sub
When I run this piece of code: Nothing happens. There is no line breaks in the output console or anything. Like the code never reached the UploadFileCompleted Sub. It doesn't seem to reach the UploadProgressCallback either, since the progress bar and the associated labels never update.
When I try to comment out the client.Headers.Set("authKey", login.authKey) line: The file seems to upload, but upon completion: I get a 403 Forbidden message as expected because I didn't have the authKey header set.
What am I doing wrong here?
I found my problem. The problem was caused by login.authKey being inaccessible to the thread it was called from. Since the program is multi-threaded: I had to make sure it was accessible to the thread. When I made it accessible: The function worked as expected.
Hope this helps other people behind me. :)
Okay, so I have been trying to download a large file with different methods. In my code what would usually work best is My.Computer.NetWork.DownloadFile but because the file is 1.5Gb's my windows form freezes and doesn't respond. I didn't bother waiting to see for how long it wouldn't respond for after I waited 5 minutes because I thought it would just be a waste of time. So I also tried wc.DownloadFileAsync (wc standing for Web Client) this works and doesn't freeze my windows form but the problem with this is that it skips over it and doesn't wait until the download is finished so it continues on with my code and therefore I get errors.
I tried researching ways to pause or stop the code until the download was finished but no luck. After further research I found the backgroundworker class. I was wondering if this would work for me and how would I implement it into my code, or if there is any easier way to go about doing this?
I was not able to successfully implement it into my code. I wasn't able to invoke and therefore got errors such as this: Cross-thread operation not valid.
This is currently my code, with the background worker:
Private Sub BackgroundWorker1_DoWork(ByVal sender As System.Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
Dim downloader As New System.Net.WebClient
Dim ServerVersion = wc.DownloadString("http://127.0.0.1:8080/patch/PatchList.txt").Trim
Dim tLines As String() = ServerVersion.Split(Environment.NewLine)
For Each NewLine As String In tLines
Dim tVersionAndUrl As String() = NewLine.Split(vbTab)
Dim encText As New System.Text.UTF8Encoding()
Dim btText() As Byte
btText = encText.GetBytes(tVersionAndUrl(0))
'MessageBox.Show(btText.ToString)
'MessageBox.Show(tVersionAndUrl(0)(0))
If tVersionAndUrl.Length < 2 Then
Exit For
End If
If Integer.Parse(tVersionAndUrl(0)) < Integer.Parse(CVersion.Text) Then
Dim TempPath As String = "\launcher\temp.rar"
AddHandler wc.DownloadProgressChanged, AddressOf ProgressChanged
AddHandler wc.DownloadProgressChanged, AddressOf ProgressChanged
AddHandler wc.DownloadFileCompleted, AddressOf DownloadCompleted
'wc.DownloadFileAsync(New Uri(tVersionAndUrl(1)), Me.GetFileName(tVersionAndUrl(1)))
wc.DownloadFileAsync(New Uri(tVersionAndUrl(1)), tmp, Stopwatch.StartNew)
'My.Computer.FileSystem.DeleteFile(Me.GetFileName(tVersionAndUrl(1)))
CVersion.Text = tVersionAndUrl(0)
LabelStatus.Text = "Download in Progress"
Button1.Enabled = False
End If
Next
MsgBox("Client is up to date")
End Sub
And this is the Addhandlers for it:
Private Sub ProgressChanged(ByVal sender As Object, ByVal e As DownloadProgressChangedEventArgs)
Dim bytesIn As Double = Double.Parse(e.BytesReceived.ToString())
Dim totalBytes As Double = Double.Parse(e.TotalBytesToReceive.ToString())
Dim percentage As Double = bytesIn / totalBytes * 100
ProgressBarCurrent.Value = Int32.Parse(Math.Truncate(percentage).ToString())
Dim BytesDownloaded As String = (e.BytesReceived / (DirectCast(e.UserState, Stopwatch).ElapsedMilliseconds / 1000.0#)).ToString("#")
If BytesDownloaded < 1024 Then
Dim Bs As String = Convert.ToInt32(BytesDownloaded)
Label4.Text = (Bs & " B/s")
ElseIf BytesDownloaded < 1048576 Then
Dim KBs As String = Math.Round(BytesDownloaded / 1024, 2)
Label4.Text = (KBs & " KB/s")
ElseIf BytesDownloaded < 1073741824 Then
Dim MBs As String = Math.Round(BytesDownloaded / 1048576, 2)
Label4.Text = (MBs & " MB/s")
End If
End Sub
Private Sub DownloadCompleted(ByVal sender As Object, ByVal e As System.ComponentModel.AsyncCompletedEventArgs)
'MessageBox.Show("Download Complete")
LabelStatus.Text = "Download Complete"
Button1.Enabled = True
Downloading = False
End Sub
I would appreciate any help. Thanks.
In newer VB/C# .net the keywords you are interested in are Await and Async:
http://msdn.microsoft.com/en-us/library/vstudio/hh191443.aspx?cs-save-lang=1&cs-lang=vb#code-snippet-1
Also you should design your routine to download in chunks. There are many ways to do that but maybe you are better to look at HTTPWebRequest/Response will handles chunking and has a method to work with +2GB files easily (ranging).
Sorry not an answer as I don't have enough rep to put this as a comment.
Cheers,
Al
In your code, we can see that you have used BackgroundWorker class, which seems good to me. What i am trying to find is the remaining delegate functions of the BackgroundWorker class like ProgressChanged, RunWorkerCompleted (instead you have registered you own delegates which might seems to lead to an error on the later stage). Try handling these events, it should help you and let you know when the task completes and reports progress in the meanwhile. For reporting progress, in the main UI thread, you can also run a progress bar with style property set as Marquee.
I have a super simple script that I am using to separate a long list of phone numbers we've gathered from donors over the years into separate area code files.
Obviously, when you have almost 1 million lines it's going to take a while - BUT - if I put in 1,000 it takes less than a second. Once I put in 1 million it takes 10 seconds to do only 5 lines. How could this be?
Imports System.IO
Public Class Form1
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
System.Windows.Forms.Control.CheckForIllegalCrossThreadCalls = False
BackgroundWorker1.RunWorkerAsync()
End Sub
Private Sub BackgroundWorker1_DoWork(ByVal sender As System.Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
System.Windows.Forms.Control.CheckForIllegalCrossThreadCalls = False
Dim lines As String
lines = RichTextBox1.Lines.Count
Dim looper As String = 0
Dim path As String = "c:\acs\"
MsgBox("I have " + lines + " lines to do.")
If (Not System.IO.Directory.Exists(path)) Then
System.IO.Directory.CreateDirectory(path)
End If
Dim i As String = 0
For loops As Integer = 0 To RichTextBox1.Lines.Count - 1
Dim ac As String = RichTextBox1.Lines(looper).ToString
ac = ac.Substring(0, 3)
Dim strFile As String = path + ac + ".txt"
Dim sw As StreamWriter
sw = File.AppendText(strFile)
sw.WriteLine(RichTextBox1.Lines(looper))
sw.Close()
Label1.Text = String.Format("Processing item {0} of {1}", looper, lines)
looper = looper + 1
Next
MsgBox("done now")
End Sub
End Class
Each time you use the RichTextBox.Lines properties, VB.Net will need to split the content by CR+LF pair. Thus your For loops As Integer = - To RichTextBox1.Lines.Count-1 is really a performance hit.
Try to use:
For Each vsLine As String in RichTextBox1.Lines
instead. It will be a lot faster. Alternatively, if you must use For loop, then get it once:
Dim vasLines() As String = RichTextBox1.Lines
For viLines As Integer = 0 to UBound(vasLines.Count)
'....
Next
instead.
First, you're performing UI updates in your For loop. That will take time.
You're updating the UI in a thread that is not the main thread which might impact performance. You should not use the CheckForIllegalCrossThreadCalls method. You should update the UI properly using the ReportProgress method of the BackgroundWorker.
You are opening and closing a file for each iteration of the loop. That will take time as well.
I think a better method would be to add the data to a Dictionary(Of String, List(Of String)), with the area code as the key and the list would hold all the number for that area code. Once the Dictionary is filled, loop through the keys and write the numbers out.
I have been pulling my hair out trying to get this to work. If I step through the code in debugger it all works great.
My problem is if I just run it, only the last task responds. I'm guessing I am overwriting the background working or something. I am sure I am doing a few things wrong but my code is now messy as I tried many way while searching. I know of the threadpool and .Net 4.0 tasks but having a hard time getting to do what I need.
Basicly I am writing a program (trying more likely) that takes a list of computers and pings then, then checks their uptime and reports back.
This works fine in the UI thread (Obviously that locks up my screen). I can have the background worker just do this, but then it does each computer 1 by one, and while the screen is responsive it still takes a long time.
So my answer was to have a for loop for each server launching a new background worker thread. My solution does not work.
I have seen other threads that I could do it, but I need to use with events to call code to update to UI when each is done.
What is the most simple way to do this?
Here is my code. Most is just copy paste + modify till I get it working right.
So In the main class I have the testworker.
(I tried using Testworker() but it said I could not do that WithEvents)
When I click the button the list loads.
Private WithEvents TestWorker As System.ComponentModel.BackgroundWorker
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles Button1.Click
Button1.IsEnabled = False
Dim indexMax As Integer
indexMax = DataGridStatus.Items.Count
For index = 1 To (indexMax)
Dim Temp As ServerInfo = DataGridStatus.Items(index - 1)
Temp.Index = index - 1
Call_Thread(Temp)
Next
End Sub
Private Sub Call_Thread(ByVal server As ServerInfo)
Dim localserver As ServerInfo = server
TestWorker = New System.ComponentModel.BackgroundWorker
TestWorker.WorkerReportsProgress = True
TestWorker.WorkerSupportsCancellation = True
TestWorker.RunWorkerAsync(localserver)
End Sub
Private Sub TestWorker_DoWork(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles TestWorker.DoWork
Dim iparray As IPHostEntry
Dim ip() As IPAddress
Dim Server As ServerInfo
Server = e.Argument
Try
'Get IP Address first
iparray = Dns.GetHostEntry(Server.ServerName)
ip = iparray.AddressList
Server.IPAddress = ip(0).ToString
'Try Pinging
Server.PingResult = PingHost(Server.ServerName)
If Server.PingResult = "Success" Then
'If ping success, get uptime
Server.UpTime = GetUptime(Server.ServerName)
Else
Server.PingResult = "Failed"
End If
Catch ex As Exception
Server.PingResult = "Error"
End Try
TestWorker.ReportProgress(0, Server)
Thread.Sleep(1000)
End Sub
Private Sub TestWorker_ProgressChanged(ByVal sender As Object, ByVal e As System.ComponentModel.ProgressChangedEventArgs) Handles TestWorker.ProgressChanged
Dim index As Integer
Dim serverchange As ServerInfo = DirectCast(e.UserState, ServerInfo)
index = DataGridStatus.Items.IndexOf(serverchange)
' index = serverchange.Index
DataGridStatus.Items.Item(index) = serverchange
' ProgressBar1.Value = e.ProgressPercentage
DataGridStatus.Items.Refresh()
End Sub
You are only getting the last result because you are blowing away your BackgroundWorker each time you call TestWorker = New System.ComponentModel.BackgroundWorker. Since the processing is being done asynchronously, this line is being called multiple times within your for loop before the previous work has finished.
Something like the following might work. (Sorry, my VB is rusty; there are probably more efficient ways of expressing this.)
Delegate Function PingDelegate(ByVal server As String) As String
Private _completedCount As Int32
Private ReadOnly _lockObject As New System.Object
Dim _rnd As New Random
Private _servers As List(Of String)
Private Sub GoButton_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles GoButton.Click
_servers = New List(Of System.String)(New String() {"adam", "betty", "clyde", "danny", "evan", "fred", "gertrude", "hank", "ice-t", "joshua"})
_completedCount = 0
ListBox1.Items.Clear()
GoButton.Enabled = False
BackgroundWorker1.RunWorkerAsync(_servers)
End Sub
Private Sub BackgroundWorker1_DoWork(ByVal sender As System.Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
Dim servers As List(Of System.String) = DirectCast(e.Argument, List(Of System.String))
Dim waitHandles As New List(Of WaitHandle)
For Each server As System.String In servers
' Get a delegate for the ping operation. .Net will let you call it asynchronously
Dim d As New PingDelegate(AddressOf Ping)
' Start the ping operation async. When the ping is complete, it will automatically call PingIsDone
Dim ar As IAsyncResult = d.BeginInvoke(server, AddressOf PingIsDone, d)
' Add the IAsyncResult for this invocation to our collection.
waitHandles.Add(ar.AsyncWaitHandle)
Next
' Wait until everything is done. This will not block the UI thread because it is happening
' in the background. You could also use the overload that takes a timeout value and
' check to see if the user has requested cancellation, for example. Once all operations
' are complete, this method will exit scope and the BackgroundWorker1_RunWorkerCompleted
' will be called.
WaitHandle.WaitAll(waitHandles.ToArray())
End Sub
Private Sub BackgroundWorker1_ProgressChanged(ByVal sender As System.Object, ByVal e As System.ComponentModel.ProgressChangedEventArgs) Handles BackgroundWorker1.ProgressChanged
ListBox1.Items.Add(String.Format("{0} ({1}% done)", e.UserState, e.ProgressPercentage))
End Sub
Private Sub BackgroundWorker1_RunWorkerCompleted(ByVal sender As System.Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
GoButton.Enabled = True
End Sub
Private Function Ping(ByVal server As System.String) As System.String
' Simulate a ping with random result and duration
Threading.Thread.Sleep(_rnd.Next(1000, 4000))
Dim result As Int32 = _rnd.Next(0, 2)
If result = 0 Then
Return server & " is ok"
Else
Return server & " is down"
End If
End Function
Private Sub PingIsDone(ByVal ar As IAsyncResult)
' This method is called everytime a ping operation completes. Note that the order in which
' this method fires is completely independant of the order of the servers. The first server
' to respond calls this method first, etc. This keeps optimal performance.
Dim d As PingDelegate = DirectCast(ar.AsyncState, PingDelegate)
' Complete the operation and get the result.
Dim pingResult As String = d.EndInvoke(ar)
' To be safe, we put a lock around this so that _completedCount gets incremented atomically
' with the progress report. This may or may not be necessary in your application.
SyncLock (_lockObject)
_completedCount = _completedCount + 1
Dim percent As Int32 = _completedCount * 100 / _servers.Count
BackgroundWorker1.ReportProgress(percent, pingResult)
End SyncLock
End Sub
Update: I posted this answer focusing on exactly what you were trying to do from a technical standpoint (use many background workers) without really putting much thought into whether or not this was a good way to accomplish your real objective. In fact, I think you could achieve what you're going for much more easily with a single BackgroundWorker and something like a Parallel.ForEach loop in its DoWork event handler (this takes care of a lot of the nitty gritty work in, e.g., Dave's solution).
When you declare WithEvents TestWorker As BackgroundWorker in VB it wraps it up something like this (not exactly—this is just to illustrate the idea):
Private _TestWorker As BackgroundWorker
Private Property TestWorker As BackgroundWorker
Get
Return _TestWorker
End Get
Set(ByVal value As BackgroundWorker)
' This is all probably handled in a more thread-safe way, mind you. '
Dim prevWorker As BackgroundWorker = _TestWorker
If prevWorker IsNot Nothing Then
RemoveHandler prevWorker.DoWork, AddressOf TestWorker_DoWork
' etc. '
End If
If value IsNot Nothing Then
AddHandler value.DoWork, AddressOf TestWorker_DoWork
' etc. '
End If
_TestWorker = value
End Set
End Property
When you realize this, it becomes clear that by setting TestWorker to a new BackgroundWorker on every call to Call_Thread, you are removing any attached handlers from the object previously referenced by the field.
The most obvious fix would simply be to create a new local BackgroundWorker object in each call to Call_Thread, attach the handlers there (using AddHandler and RemoveHandler), and then just let it do its thing:
Private Sub Call_Thread(ByVal server As ServerInfo)
Dim localserver As ServerInfo = server
' Use a local variable for the new worker. '
' This takes the place of the Private WithEvents field. '
Dim worker As New System.ComponentModel.BackgroundWorker
' Set it up. '
With worker
.WorkerReportsProgress = True
.WorkerSupportsCancellation = True
End With
' Attach the handlers. '
AddHandler worker.DoWork, AddressOf TestWorker_DoWork
AddHandler worker.ProgressChanged, AdressOf TestWorker_ProgressChanged
' Do the work. '
worker.RunWorkerAsync(localserver)
End Sub
Creating the worker right there in the method should be fine as long as you do so from the UI thread, since BackgroundWorker automatically attaches to the current SynchronizationContext in its constructor (if I remember correctly).
Ideally you should use only 1 backgroundworker and use it like this:
Assemble all the work that needs to be done: in your case a list of ServerInfo
Do the work in the background: ping all the servers and keep the result
Report progress: for example after each server pinged
Put results back in DoWorkEventArgs.Result
Display the results back in your UI.
You need to attach TestWorker_DoWork and TestWorker_ProgressChanged to the DoWork and ProgressChanged events within Call_Thread. I haven't yet examined the rest of the code, but that is why it isn't doing anything now.
TestWorker = New System.ComponentModel.BackgroundWorker
TestWorker.WorkerReportsProgress = True
TestWorker.WorkerSupportsCancellation = True
AddHandler TestWorker.DoWork, AddressOf TestWorker_DoWork
AddHandler TestWorker.ProgressChanged, AddressOf TestWorker_ProgressChanged
TestWorker.RunWorkerAsync(localserver)