1

I'm downloading files .mp3 and my goal is not to have even a minimum GUI freezing during downloading. My aim is also to display the bites received in a progress bar and through labels. This code is working, but sometimes is freezing without any reason, sometimes the progress bar doesn't work until file is completely done. So far, this is the "best" code I found online for a completely working progress bar during a download, but still gets problems. How do you think I can increase performances? How can I make a resistant and reliable working progressbar? How can I download also large file without GUI freezing? I tried (curiosity) to download a 600 mb file and it completely freeze, not responding and not giving any issue back. Thanks

EDIT1: I'm trying with this,eventhough I'm lost on high waves.. Any idea on how can I use this code and insert it into Jimi Answer? Answer


Imports System.IO
Imports System.IO.Path
Imports System.Net



Public Class Form1
  Private downloader As MyDownloader = Nothing

  Private Sub btnStartDownload_Click(sender As Object, e As EventArgs) Handles btnStartDownload.Click
      Dim progress = New Progress(Of String)(
          Sub(data)

              MsgBox("we are on the UI thread here")
          End Sub)

      Dim url As Uri = New Uri(TextBox1.Text)
      downloader = New MyDownloader()
      'How can I remove this second? I don't need download from url every 1 second. 
      downloader.StartDownload(progress, url, 1)

  End Sub

And

Imports System.ComponentModel
Imports System.Diagnostics
Imports System.Net
Imports System.Net.Http
Imports System.Text.RegularExpressions
Imports System.Threading

Public Class MyDownloader
    Private Shared ReadOnly client As New HttpClient()
            client.DownloadProgressChanged += AddressOf Client_DownloadProgressChanged
            client.DownloadFileCompleted += AddressOf Client_DownloadFileCompleted
    Private interval As Integer = 0
    Private Sub Client_DownloadFileCompleted(ByVal sender As Object, ByVal e As AsyncCompletedEventArgs)
        System.Windows.Forms.MessageBox.Show("Download OK!", "Message", MessageBoxButtons.OK, MessageBoxIcon.Information)
    End Sub
    Public Sub StartDownload(progress As IProgress(Of String), url As Uri, intervalSeconds As Integer)
        interval = intervalSeconds * 1000
        Task.Run(Function() DownloadAsync(progress, url))
    End Sub
    Private Sub Client_DownloadProgressChanged(ByVal sender As Object, ByVal e As DownloadProgressChangedEventArgs)
        ProgressBar1.Minimum = 0
        Dim receive As Double = Double.Parse(e.BytesReceived.ToString())
        Dim total As Double = Double.Parse(e.TotalBytesToReceive.ToString())
        Dim percentage As Double = receive / total * 100
        label2.Text = $"{String.Format("{0:0.##}", percentage)}%"
        ProgressBar1.Value = Integer.Parse(Math.Truncate(percentage).ToString())
    End Sub
    Private Async Function DownloadAsync(progress As IProgress(Of String), url As Uri) As Task
        Dim pattern As String = "<(?:[^>=]|='[^']*'|=""[^""]*""|=[^'""][^\s>]*)*>"
        Dim downloadTimeWatch As Stopwatch = New Stopwatch()
        downloadTimeWatch.Start()
        Do
            Try
                Dim response = Await client.GetAsync(url, HttpCompletionOption.ResponseContentRead)
                Dim data = Await response.Content.ReadAsStringAsync()

                data = WebUtility.HtmlDecode(Regex.Replace(data, pattern, ""))
                progress.Report(data)

                Dim delay = interval - CInt(downloadTimeWatch.ElapsedMilliseconds)
                Await Task.Delay(If(delay <= 0, 10, delay))
                downloadTimeWatch.Restart()

            Catch ex As Exception
            End Try
        Loop
    End Function
End Class

I'm Seriously lost on it, I tried to delete cancel download as I am not going to stop any download and I tried also to delete Download from url every 1 second as I just need one time download for every link. Thanks

Mattia
  • 258
  • 7
  • 25
  • 3
    @user2864740 Hi, thanks for your fast answer. Please see my edit. Thanks I edited the title. – Mattia Sep 24 '20 at 01:44
  • Does this answer your question? [VB.NET progressbar backgroundworker](https://stackoverflow.com/questions/13206926/vb-net-progressbar-backgroundworker) – Jeremy Thompson Sep 24 '20 at 01:48
  • @Mattia can you please [edit] your question with the correct formatting: https://stackoverflow.com/help/formatting - also see the duplicate I've closed linked. – Jeremy Thompson Sep 24 '20 at 01:49
  • Does the GetRemoteFileSize terminate quickly? It should, being a HEAD, although I’d verify that.. as, on a quick glance, the other code appears reasonably asynchronous (via DownloadFileAsync and callbacks). I’d not be surprised if it crashed, although I would not expect it to “freeze the UI”. – user2864740 Sep 24 '20 at 01:53
  • The callback has e.TotalBytesToRecieve, so assuming the server sends back the size there is no reason for the initial HEAD request. The progress updates should ‘possibly’ be marshaled back to the UI explicitly, although that does not address a “freezing” state as WebForms will throw an exception if it detects an off-thread access. – user2864740 Sep 24 '20 at 01:57
  • 1
    If you apply the code I posted for [your previous question](https://stackoverflow.com/a/64001853/7444103) to this context, you can achieve this easily. Since you'll be using all async methods for this, you can remove the Task as well and do all async/await. The `IProgress` is there exactly for this purpose: update the UI at intervals that you **have** to calculate, based on the size of the file you're downloading. – Jimi Sep 24 '20 at 02:00
  • @Jimi 1) That assumes the DownloadProgressChanged runs off the UI thread (which I also assume, although perhaps it is marshaled already like a button click event?); 2) I would expect an unhandled exception (and thus program crash?) of accessing the progress control off the UI thread, not a “freeze”, as WinForms does not marshal implicitly .. or maybe it’s been too long since I’ve had to muck with Winforms *shrug* – user2864740 Sep 24 '20 at 02:04
  • 1
    @user2864740 I'm not sure why you're telling me this, since this is exatcly what the answer I linked is doing: using a `Progress` delegate to marshal progress information from a ThreadPool Thread to the UI Thread. A Task that, given the intrinsic IO bound nature of these operations (those in this question), is not even required (async/await is enough). `Progress` can be used anyway. – Jimi Sep 24 '20 at 02:09
  • 2
    Hi Jimi, I am quite new on async so I m doing my little "experiments" before to have an idea and apply it to a program. I want to say also I dont work with code but I play so this is not my job. It s my hobby let s say. I couldnt use your previous code as I dont know how to implement the use of a progress bar with your code but I m pretty sure it is the best way to do it. So since my chances are few, I found this code on msdn and it seems the best I can google so far that got an "alive" progressbar. – Mattia Sep 24 '20 at 03:13
  • @jeremyThomson Sprry as i wrote in my question, I tried but I cant get it formatted!? Some of the code, ramdomly, goes out of the tags and it block me from posting my question.. would you be able to try for me? Thanks – Mattia Sep 24 '20 at 03:15
  • @Jimi - as in all my other comments 1) This code _is_ asynchronous already, per the use of DownloadFileAsync; 2) I would expect attempting to update a WinForms component from a callback that is not marshaled back to the UI thread to cause a program crash, or even throw suppressed exceptions which might result in the progress bar not being updated, not a “freeze”. – user2864740 Sep 24 '20 at 04:30
  • So unless I’m completely misunderstanding “***it completely freeze, not responding** and not giving any issue back*..”, I fail to see how the proposed solution actual addresses such a “freeze”. – user2864740 Sep 24 '20 at 04:32
  • 1
    @user2864740 I understand. My suggestion is related to the code I linked (see what it does and how it's *timed*). `DownloadFileAsync` is event driven ([AsyncOperation](https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.asyncoperation)), different from `DownloadFileTaskAsync`. When the callback kicks in, you (the OP) cannot think that, since the operation is supposed to be asynchronous, you can do whatever you want in the event handler, which is called to a pace that you don't directly control. Or call `Refresh()` (synchronous) on any Control. [...] – Jimi Sep 24 '20 at 05:27
  • @user2864740 What usually happens (you may have experience with the FileSystemWatcher class) is that these events are raised too often in relation to what other code is processing synchronously. If you do the same thing with a FSW, the buffer fills up and you start loosing events. The general rule is always the same: don't block (ever, in any case, no matter what the pattern) on async code. What *freeze* means here is undefined, but there are too many mistakes in that code, there's no surprise it may *misbehave*. – Jimi Sep 24 '20 at 05:28
  • @user2864740 My suggestion is to perform all operations and calculations on the async side (Task or async methods that you control) and update the UI with the bare minimum data required for the presentation you have planned. As shown in the code I linked. – Jimi Sep 24 '20 at 05:28
  • HI Jimi, now I m going to try If I can shape my code to use it correctly like yours. I ll come back if any. Thanks – Mattia Sep 24 '20 at 10:49
  • 2
    @Jimi , I tried as best as I could and probably I'm doing something over my possibilities, I edited the code and tried to insert a progress bar on your code, I tried to remove download every 1 second and token as I m not interested on stopping a download, but all this is bringing some more problem, as I can't declare progressbar 1 and label 2.. How can I try to sort this? Thanks – Mattia Sep 24 '20 at 12:43
  • Nope, you didn't really *try*. You have `ReadOnly client As New HttpClient()`, but you slapped in `client.DownloadProgressChanged += AddressOf ...` which comes from WebClient. HttpClient doesn't have events, even less events that come from another class. If you don't know how HttpClient works (you should really learn its basics), then use WebClient, but use it right. As described, don't clog the event handlers: it's very important. Also, see the Docs about its functionality. E.g., you're calculating the download *percentage*: `e.ProgressPercentage` already gives you that. And so on. – Jimi Sep 24 '20 at 14:32
  • 1
    Remove any `Refresh()` things you have there an anything that tries to manage Controls synchronously in the handler of the event that notifies a download progress. This event must do one thing, one thing only and only something that can be processed as fast as possible. No distractions, no detours, no fancy stuff. I.e, don't take the risk to generate cascading events caused by actions coming from the handler. – Jimi Sep 24 '20 at 14:43
  • 1
    Hi Jimi, thanks for your reply. I assume my lack of understanding is literally blocking me to solve this "simple" case. I'm constantly trying to google to find documentation etc but it comes difficult to impleement on my code. I will keep reading and in case of, I'll come back here to post the solution. Thanks – Mattia Sep 24 '20 at 14:49

0 Answers0