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. :)
Related
I need to download a large file (about 3-5GB) in my application. The file is generated dynamically on request, so i can't predict, when it is ready for download. I need to try the download, and when i get a 404, i have to wait and retry later.
The download is async because i have a progressbar.
I tried also to put a "normal" download (WC.DownloadFile(...)) in a try..catch, but didn't solve my problem.
Private Sub DownloadUpdate()
Dim RndName As String = IO.Path.GetRandomFileName
UpdateTmpPath = IO.Directory.CreateDirectory(IO.Path.Combine(IO.Path.GetTempPath, RndName)).FullName
UpdateTmpFile = UpdateTmpPath & "\update.zip"
UpdateUnzipDir = IO.Directory.CreateDirectory(UpdateTmpPath & "\update").FullName
Log(UpdateTmpFile)
WC.DownloadFileAsync(New Uri(url), UpdateTmpFile)
End Sub
btw sorry for my english, it's not my first language :)
Found the answer :)
By adding the handler "DownloadFileCompleted" i'm able to check the Http-Status:
Private Sub AfterDownload(ByVal sender As Object, ByVal e As Object) Handles WC.DownloadFileCompleted
Dim status As HttpStatusCode = DirectCast(CType(e.Error, WebException).Response, HttpWebResponse).StatusCode
If status = HttpStatusCode.NotFound Then
"...wait and retry download"
Else
"...do something with downloaded file"
End If
End Sub
Hope it helps someone.
Daniel
I relise there is so many other questions out there regarding the progressbar, though I've looked through them "all" and can not find one that works.
I am trying to upload c:\screenshot.png to my ftp with a progress bar and a msgbox once finished.
Could someone provide a working example for me?
Thankyou
Edit heres the code I tried. Uploading works, though the progress bar dosent.
Sub UpdateProgressBar(ByVal sender As Object, ByVal e As UploadProgressChangedEventArgs)
If ProgressBar1.InvokeRequired Then
ProgressBar1.Invoke(New UploadProgressChangedEventHandler(AddressOf UpdateProgressBar), sender, e)
Exit Sub
End If
ProgressBar1.Value = CInt(ProgressBar1.Minimum + _
((ProgressBar1.Maximum - ProgressBar1.Minimum) * _
e.ProgressPercentage) / 100)
End Sub
Private Sub btnUpload_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button8.Click
Label16.Text = "Uploading now..."
Label16.Update()
Dim client As New System.Net.WebClient()
AddHandler client.UploadProgressChanged, AddressOf UpdateProgressBar
With client
.Credentials = New NetworkCredential( _
"damon#slimar.eu", "mine123!")
.UploadFile("ftp://slimar.eu/screenshot.png", "C:\screenshot.png")
End With
Label16.Text = "Done!"
Label16.Update()
End Sub
Progress bar has minValue,Max value, StepValue which is used to perform a step and Value to setup arbitray value.When you uploading a file or downloading you should be able to see via e paramenter total byte and actual byte trasmission.So you can setup Progress bar value and max value.
Also personally i invite you to use backgroundworker which :
Not Freeze GUI
Give you much controll on thread with no issue and no invoke needs
Make it more simple :)
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 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)
I am having the worst trouble getting around a bug, and am hoping that I can get some advice on this site. In short, I am trying to make an asynchronous web service call from my VB.NET application. But my "client_DownloadDataCompleted" callback is NEVER being called when the download is complete.
Here is my complete code:
Public Sub BeginAsyncDownload(ByVal Url As String)
Dim waiter As System.Threading.AutoResetEvent = New System.Threading.AutoResetEvent(False)
Dim client As WebClient = New WebClient()
'client_DownloadDataCompleted method gets called when the download completes.
AddHandler client.DownloadDataCompleted, AddressOf client_DownloadDataCompleted
Dim uri As Uri = New Uri(Url)
Downloading = True 'Class variable defined elsewhere
client.DownloadDataAsync(uri, waiter)
End Sub
Private Sub client_DownloadDataCompleted(ByVal sender As Object, ByVal e As AsyncCompletedEventArgs)
MessageBox.Show("Download Completed")
Downloading = False
Debug.Print("Downloaded")
End Sub
Again, the client_DownloadDataCompleted method is never being called. I have also tried using the method:
Private Sub client_DownloadDataCompleted(ByVal sender As Object, ByVal e As DownloadDataCompletedEventArgs)
With no luck. What I really need is that "Downloading" variable to be switched off when the download is complete.
Thanks in advance!
Brett
The client (Webclient) should be declared outside the BeginAsyncDownload subroutine, so it has a form/class level visibility. Please refer to the following code:
Public Class Form1
Dim client as New WebClient()
Private Sub BeginAsyncDownload(ByVal Url As String)
AddHandler client.DownloadDataCompleted, AddressOf client_DownloadDataCompleted
Dim uri As Uri = New Uri(Url)
Downloading = True 'Class variable defined elsewhere
client.DownloadDataAsync(uri, waiter)
End Sub
Private Sub client_DownloadStringCompleted(ByVal sender As Object, ByVal e As System.Net.DownloadStringCompletedEventArgs)
MessageBox.Show("Download Completed")
Downloading = False
Debug.Print("Downloaded")
End Sub
This is a tough one. I spent a little time on this and wasn't able to figure out why it wasn't getting called, sorry.
If you aren't able to get this to work, I have some code on CodePlex that includes a WebHelper class that might help you. I tried to make it as easy to use as WebClient but with all the power of HttpWebRequest.
The project is called BizArk. I wrote it just as a repository of code for myself. Feel free to just use the bits you want, I don't have any particular interest in how the code is used (as long as it's not used for evil :).
In what context are you invoking the webclient? WebClient will pick up your SynchronizationContext.Current and post its completion callback to it.
If you are using WinForms and your UI thread is blocked it will never be able to process your callback.