Vb.net WebClient异步下载程序工作不正常
所以,我已经花了一段时间研究这个问题了。我是一个业余程序员,所以我并不总是知道我做错了什么 无论如何,我最近项目的前提是: 我和我的朋友们经常玩MineCraft,但他们并不特别聪明,我们也不总是能得到mods并给他们发送链接等等。所以我想我应该编写一些程序来自动拉下MOD,使它们与服务器同步,同时获取服务器数据 我使用的是免费的FTP主机,但我不认为这是问题所在,原因将变得很清楚 基本上,我想使用progressbar和理想情况下的标签来指示整个数据块的进度(所有MOD加在一起…不超过1GB-相当小)。但是,关于异步选项,我似乎遇到了一些问题:Vb.net WebClient异步下载程序工作不正常,vb.net,multithreading,winforms,asynchronous,webclient,Vb.net,Multithreading,Winforms,Asynchronous,Webclient,所以,我已经花了一段时间研究这个问题了。我是一个业余程序员,所以我并不总是知道我做错了什么 无论如何,我最近项目的前提是: 我和我的朋友们经常玩MineCraft,但他们并不特别聪明,我们也不总是能得到mods并给他们发送链接等等。所以我想我应该编写一些程序来自动拉下MOD,使它们与服务器同步,同时获取服务器数据 我使用的是免费的FTP主机,但我不认为这是问题所在,原因将变得很清楚 基本上,我想使用progressbar和理想情况下的标签来指示整个数据块的进度(所有MOD加在一起…不超过1GB-
- 它将随机选择不下载它应该下载的文件
- 在声明文件完整之前,它可能不会下载完整的文件
- 当msgbox触发表示已完成下载所有项目时,progressbar可能已满50%
- 有没有更好的方法来实现这一点
Public Function GetDownloadSize(ByVal URL As String) As Long
Dim request As Net.FtpWebRequest = DirectCast(Net.WebRequest.Create(URL), Net.FtpWebRequest)
request.Method = Net.WebRequestMethods.Ftp.GetFileSize
request.Credentials = New Net.NetworkCredential(dl_user, dl_pass)
Dim response As Net.FtpWebResponse = DirectCast(request.GetResponse(), Net.FtpWebResponse)
Dim fileSize As Long = response.ContentLength
Return fileSize
End Function
Private Sub btn_sync_Click(sender As Object, e As EventArgs) Handles btn_sync.Click
Dim cont As DialogResult = MsgBox("Continue? " + (total_dl_size / 1000).ToString("N0") + " KB remain to be downloaded.", MsgBoxStyle.YesNo, "CAUTION!")
If cont = DialogResult.No Then
tb_warnings.AppendText("-ERR: User declined to synchronize files. Restart the application to sync.")
tb_warnings.AppendText(ControlChars.NewLine)
Label3.BackColor = Color.Firebrick
Return
End If
btn_sync.Enabled = False
btn_scan.Enabled = false
tb_warnings.AppendText("-Deleting outmoded/unused mods. Protected mods will be kept.")
For Each i As fdata_obj In deleted_files
My.Computer.FileSystem.DeleteFile(mc_dir + "\mods\" + i.name)
Next
tb_warnings.AppendText(ControlChars.NewLine)
tb_warnings.AppendText("-Deleting mod subdirectories to ensure no conflicts.")
tb_warnings.AppendText(ControlChars.NewLine)
For Each d In My.Computer.FileSystem.GetDirectories(mc_dir + "\mods")
My.Computer.FileSystem.DeleteDirectory(d, FileIO.DeleteDirectoryOption.DeleteAllContents)
Next
initialize_download()
End Sub
Private Sub initialize_download()
Dim wc As New System.Net.WebClient() ' SORRY, ASSUME THIS IS A PUBLIC VAR SO IT CAN BE REFERENCED ACROSS ITS OTHER METHODS
AddHandler wc.DownloadProgressChanged, AddressOf OnDownloadProgressChanged
AddHandler wc, AddressOf OnFileDownloadCompleted
Dim usr As String = "randouser"
Dim pass As String = "randopass"
For Each s In (From dl As fdata_obj In new_files Select dl_server + "/mods/" + mods_dir + "/" + dl.name).ToList
downloads.Enqueue(s)
Next
wc.Credentials = New Net.NetworkCredential(usr, pass)
Dim urix As String = downloads.Dequeue
Try
wc.DownloadFileasync(New Uri(urix), mc_dir + "\mods\" + IO.Path.GetFileName(urix))
Catch ex As Exception
MsgBox(ex.Message)
If tb_warnings.InvokeRequired = True Then
tb_warnings.Invoke(New tb_updater(AddressOf tb_update), "-ERR: Could not download file: " + urix, urix)
Else
tb_warnings.AppendText("-ERR: Could not download file: " + IO.Path.GetFileName(urix))
tb_warnings.AppendText(ControlChars.NewLine)
End If
end try
End Sub
Private Sub OnDownloadProgressChanged(ByVal sender As Object, ByVal e As System.Net.DownloadProgressChangedEventArgs)
MsgBox("This is happening!")
total_dl = total_dl + e.BytesReceived
Dim percentage As Integer = (CType((total_dl / total_dl_size), Integer) * 100)
if percentage > 100 then
percentage = 100
endif
prog_update(percentage)
End Sub
delegate sub progress_update(byval prog as integer)
' POTENTIAL ISSUES HERE???????
private sub prog_update(byval prog as integer)
if progressbar1.invokerequired then
progressbar1.invoke(new prog_update(addressof progress),prog)
else
progressbar1.value = prog
Private Sub OnFileDownloadCompleted(ByVal sender As Net.WebClient, ByVal e As System.ComponentModel.AsyncCompletedEventArgs)
If e.Cancelled Then
MsgBox(e.Cancelled)
ElseIf Not e.Error Is Nothing Then
MsgBox(e.Error.Message)
Else
if downloads.count > 0 then
Dim urix As String = downloads.Dequeue
Try
wc.DownloadFileasync(New Uri(urix), mc_dir + "\mods\" + IO.Path.GetFileName(urix))
Catch ex As Exception
MsgBox(ex.Message)
If tb_warnings.InvokeRequired = True Then
tb_warnings.Invoke(New tb_updater(AddressOf tb_update), "-ERR: Could not download file: " + urix, urix)
Else
tb_warnings.AppendText("-ERR: Could not download file: " + IO.Path.GetFileName(urix))
tb_warnings.AppendText(ControlChars.NewLine)
End If
End Try
End If
End Sub
首先,进度条不工作的主要原因是:
Dim percentage As Integer = (CType((total_dl / total_dl_size), Integer) * 100)
代码将首先计算total\u dl/total\u dl\u size
,假设结果为0.34,然后将其转换为一个整数,结果为0(0.34向下舍入为零,因为整数没有小数),最后将0乘以100(结果仍然为0)
至于线程安全(调用),我总是使用我创建的:
Imports System.Runtime.CompilerServices
Public Module Extensions
<Extension()> _
Public Sub InvokeIfRequired(ByVal Control As Control, ByVal Method As [Delegate], ByVal ParamArray Parameters As Object())
If Parameters Is Nothing OrElse _
Parameters.Length = 0 Then Parameters = Nothing 'If Parameters is null or has a length of zero then no parameters should be passed.
If Control.InvokeRequired = True Then
Control.Invoke(Method, Parameters)
Else
Method.DynamicInvoke(Parameters)
End If
End Sub
End Module
您只需键入:
Me.InvokeIfRequired(AddressOf SomeMethod, params)
扩展方法将为您完成其余的工作
如果使用lambda表达式,则可以动态创建方法:
Me.InvokeIfRequired(Sub()
Label1.Text = "Hello world!"
ProgressBar1.Value += 1
End Sub)
现在,到你的代码 我把你的代码分开了一点,这样更容易处理。对于初学者,我没有将下载代码复制粘贴到
DownloadFileCompleted
事件处理程序,而是使用了一种更通用的方法,名为DownloadFile()
希望这有帮助 上次我检查时,
WebClient
运行良好。请向我们显示您的代码。@VisualIncent#1:用户名不错,#2:编辑后的帖子显示代码。这个委托函数调用可能有点不正确,因为我在重写这段代码时没有考虑IDE的好处,并且不太记得它的格式。请放心,呼叫模板在我的原始代码中是正确的。1:谢谢;)2:好吧,我先启动我的电脑,然后我再试试密码。你真的不应该那样分享你的密码,即使没有什么有价值的东西。请删除评论,我有自己的FTP服务器可以试用。试用并找出问题可能需要一到两个小时,因此请耐心等待。:)杰出的我会在回家后努力实现这一点。谢谢你的帮助!使下载部分正常工作是难题的最后一部分。嗯,我仍然需要编写程序来生成配置文件,但这很简单。谢谢@斯密蒂·沃伯曼森:没问题。希望它能起作用,如果不是,就告诉我@斯密蒂·沃伯曼森:请记住,我并没有包含您的所有代码。你必须自己再添加一些部件。主要是队列,它是在表单加载
事件中填充的,以及预下载的东西,它们应该在按钮点击
事件中完成。不确定,但似乎是这样。invokeFrequeuired不是一个组成方法,或者至少它抛出了错误。附录:否则,代码工作得非常出色。至于为什么progbar是愚蠢的:每次报告prog时,我都使用e.bytesreceived来更新数量,所以它的总和是不断增加的。我设置了一个新的var基线,它保存了以前的下载量,然后您可以在运行时对它们求和,以获得总体dl。非常感谢。它起作用了!万岁!
Me.InvokeIfRequired(Sub()
Label1.Text = "Hello world!"
ProgressBar1.Value += 1
End Sub)
''' <summary>
''' Downloads a file from the specified URL with the specified credentials.
''' </summary>
''' <param name="URL">The URL of the file.</param>
''' <param name="Username">The username which to login with.</param>
''' <param name="Password">The password which to login with.</param>
''' <remarks></remarks>
Private Sub DownloadFile(ByVal URL As String, ByVal Username As String, ByVal Password As String)
If wc.IsBusy = True Then Throw New Exception("A download is already ongoing!")
wc.Credentials = New NetworkCredential(dl_user, dl_pass)
total_dl_size = GetDownloadSize(URL, Username, Password)
Try
Dim FileName As String = Path.GetFileName(URL)
AppendWarning("Downloading " & FileName & "...")
wc.DownloadFileAsync(New Uri(URL), Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), FileName))
Catch ex As Exception
AppendWarning("-ERR: Could not download file: " & Path.GetFileName(URL))
End Try
End Sub
''' <summary>
''' (Thread-safe) Appends a warning or status message to the "tb_warnings" text box.
''' </summary>
''' <param name="Text">The text to append.</param>
''' <remarks></remarks>
Private Sub AppendWarning(ByVal Text As String)
Me.InvokeIfRequired(Sub() tb_warnings.AppendText(Text & Environment.NewLine))
End Sub
Private dl_user As String = "someusername"
Private dl_pass As String = "somepassword"
Private dl_urls As String() = {"URL1", "URL2"} 'Temporary. Use your own code.
Private total_dl_size As Long = 0
Private total_dl As Long = 0
Dim WithEvents wc As New System.Net.WebClient()
Dim downloads As New Queue(Of String)
Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
'Populate the download queue.
downloads.Enqueue(dl_urls(0)) 'Temporary. Use your own code here.
downloads.Enqueue(dl_urls(1))
End Sub
'The download button.
Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
'Do your pre-download stuff here.
DownloadFile(downloads.Dequeue(), dl_user, dl_pass) 'Download the first file.
End Sub
''' <summary>
''' Downloads a file from the specified URL with the specified credentials.
''' </summary>
''' <param name="URL">The URL of the file.</param>
''' <param name="Username">The username which to login with.</param>
''' <param name="Password">The password which to login with.</param>
''' <remarks></remarks>
Private Sub DownloadFile(ByVal URL As String, ByVal Username As String, ByVal Password As String)
If wc.IsBusy = True Then Throw New Exception("A download is already ongoing!")
wc.Credentials = New NetworkCredential(dl_user, dl_pass) 'Set the credentials.
total_dl_size = GetDownloadSize(URL, Username, Password) 'Get the size of the current file.
Try
Dim FileName As String = Path.GetFileName(URL) 'Get the current file's name.
AppendWarning("Downloading " & FileName & "...") 'Download notice.
wc.DownloadFileAsync(New Uri(URL), Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), FileName)) 'Download the file to the desktop (use your own path here).
Catch ex As Exception
AppendWarning("-ERR: Could not download file: " & Path.GetFileName(URL))
End Try
End Sub
''' <summary>
''' (Thread-safe) Appends a warning or status message to the "tb_warnings" text box.
''' </summary>
''' <param name="Text">The text to append.</param>
''' <remarks></remarks>
Private Sub AppendWarning(ByVal Text As String)
Me.InvokeIfRequired(Sub() tb_warnings.AppendText(Text & Environment.NewLine))
End Sub
Private Sub wc_DownloadProgressChanged(sender As Object, e As System.Net.DownloadProgressChangedEventArgs) Handles wc.DownloadProgressChanged
Me.InvokeIfRequired(Sub()
Dim Progress As Integer = CType(Math.Round((e.BytesReceived * 100) / total_dl_size), Integer)
If Progress > 100 Then Progress = 100
If Progress < 0 Then Progress = 0
ProgressBar1.Value = Progress
End Sub)
End Sub
Private Sub wc_DownloadFileCompleted(sender As Object, e As System.ComponentModel.AsyncCompletedEventArgs) Handles wc.DownloadFileCompleted
If e.Cancelled Then
MessageBox.Show(e.Cancelled)
ElseIf Not e.Error Is Nothing Then
MessageBox.Show(e.Error.Message)
Else
If downloads.Count > 0 Then
DownloadFile(downloads.Dequeue(), dl_user, dl_pass) 'Download the next file.
Else
AppendWarning("Download complete!")
End If
End If
End Sub
Path.Combine(Path1, Path2, Path3, ...)
Path.Combine("C:\", "Foo") 'Results in: C:\Foo
Path.Combine("C:\", "Foo", "Bar", "Hello World.txt") 'Results in: C:\Foo\Bar\Hello World.txt