2017-02-01 7 views
0

Итак, я вот-вот убью это на некоторое время. Я программист-любитель, и поэтому я не всегда знаю, что я делаю неправильно.Асинхронный загрузчик WebClient не работает должным образом

Во всяком случае, предпосылка моего недавнего проекта:

Моих друзья и я регулярно играть Minecraft, но они не очень яркими, и мы не всегда вокруг, чтобы получить моды и отправить их ссылку и этажерку , Поэтому я подумал, что буду программировать что-то, чтобы автоматически вытаскивать моды, чтобы они синхронизировались с сервером и одновременно получали данные сервера.

Я использую бесплатный хост FTP, но я не думаю, что это проблема здесь, по причинам, которые станут ясными.

В принципе, я хочу использовать индикатор прогресса и, в идеале, метку, чтобы указать прогресс общего блока данных (все моды вместе ... не более 1 ГБ - немного меньше). Тем не менее, я, кажется, работаю в нескольких вопросов, связанных с возможностью асинхронного:

  • Это будет случайным образом выбрать опции не загружать файлы, которые должны быть скачиванием

  • Он не может загружать файлы в полном объеме до подачи заявления должно быть заполнено

  • Ошибка выполнения может составлять 50% при запуске msgbox, говоря, что он завершил загрузку всех элементов.

Однако, в то время как прогрессбар не работает из-за событий отчетности для прогресса не существует в синхронном использовании Webclient, когда я бегу синхросигнала в BGworker, он загружает должным образом каждый раз. Тем не менее, я теряю на текущую отчетность, которая является своего рода важно ....

Так, в основном:

  • Есть ли лучший способ осуществить это?

Это последний кусок, который мне нужно для работы, прежде чем он готов к работе, поэтому я действительно хотел бы попробовать и сделать это. Спасибо за любую помощь!

Edit: обновление с кодом:

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 
+0

«WebClient» отлично работал в последний раз, когда я проверил. Пожалуйста, покажите нам свой код. –

+1

@VisualVincent # 1: Хорошее имя пользователя, # 2: Отредактированное сообщение, чтобы показать код.Этот вызов функции делегирования функции может быть немного отключен, так как я переписываю этот код без преимущества IDE и не совсем помню формат. Будьте уверены, шаблон звонка правильный в моем исходном коде. –

+0

** 1: ** Спасибо;) ** 2: ** Хорошо, я просто запустил мой компьютер, тогда я попробую код. –

ответ

1

Прежде всего, основной причиной ваш прогресс бар не работает из-за этого:

Dim percentage As Integer = (CType((total_dl/total_dl_size), Integer) * 100) 

Код будет первым оценить total_dl/total_dl_size, скажем, что это приводит к 0,34, тогда он преобразует это в целое число, которое приведет к 0 (0.34 округляется до нуля, так как целые числа не имеют десятичных знаков), и, наконец, он умножает 0 на 100 (что по-прежнему приводит к 0).
Что вам нужно сделать, так это умножить дивиденд на 100 сначала, чтобы результат был равен 0-100 вместо 0-1: (total_dl * 100)/total_dl_size.

Что касается безопасности потоков (вызывающем) Я всегда использовать эту extension method, что я создал:

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 

(предпочтительно поместить его в другой файл)

, что вместе с Lambda expressions (введен в Visual Studio 2010), будет значительно упростить вызов для вас. Это происходит потому, что вместо того, чтобы поместить If InvokeRequired шаблон везде:

If Me.InvokeRequired Then 
    Me.Invoke(New Action(AddressOf SomeMethod), params) 
Else 
    SomeMethod() 
End If 

вам нужно только ввести:

Me.InvokeIfRequired(AddressOf SomeMethod, params) 

и метод расширения остальное сделает за вас.

А если использовать лямбда-выражения можно создавать методы динамически:

Me.InvokeIfRequired(Sub() 
         Label1.Text = "Hello world!" 
         ProgressBar1.Value += 1 
        End Sub) 

Теперь ваш код.

Я отделил ваш код немного больше, так что с ним легче справиться. Во-первых, вместо копирования кода загрузки в обработчик события DownloadFileCompleted я сделал более общий метод, называемый DownloadFile().

''' <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 

Некоторые другие вещи, которые следует иметь в виду:

  • MsgBox() function существует исключительно для обратной совместимости. Вместо этого вы должны использовать стандарт .NET MessageBox.Show() method.

  • Конкатенация строк должна выполняться с использованием амперсанда (&) вместо плюса (+). See why.

  • Конкатенационные пути всегда должны выполняться с использованием Path.Combine(), так как это обеспечит создание правильного пути. Если вы введете что-нибудь недействительное, оно вызовет исключение.

    Использование:

    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 
    

Надеется, что это помогает!

+0

Отлично! Я постараюсь добиться этого, когда вернусь домой. Спасибо за помощь! Правильное выполнение части загрузки было последней частью головоломки. Ну, я все равно должен сделать программу для создания файлов конфигурации, но это тривиально. Благодаря! –

+0

@smitty_werbermanjensen: Нет проблем. Надеюсь, он работает, и если не просто скажите мне! –

+0

@smitty_werbermanjensen: Имейте в виду, что я не включил весь ваш код. Вам придется снова добавить некоторые части. В основном это очередь, которая заполняется в событии 'Form_Load', и материал предварительной загрузки, который должен выполняться в событии' Button_Click'. –

 Смежные вопросы

  • Нет связанных вопросов^_^