How to use .NET WebClient synchronously and still receive progress updates

Recently, I had to implement our custom package deployment mechanism for our UI framework where packages are hosted on a web-accessible URI. During the download process, the UI should display a progress bar which updates the user on the progress the download. After download is complete, the client performs a few more steps including

  • checking file integrity against its md5 hash
  • expanding the compressed file into the destination
  • cleaning up

The implementation uses TPL; for each package that user chose to upgrade, a task is created which performs the steps above. During this process, the UI displays a progress of each task.

The download portion of the process is implemented using WebClient.DownloadFile method. It’s short and sweet, hiding most of the complexity of opening a connection, getting a response stream and reading it one chunk at a time. But its simplicity comes a shortcoming: during download the thread doing the downloading is blocked an no progress is reported, so downloading a large file would cause the user to think that the system froze, because even though the wait spinner is spinning, the progress bar is stuck. What I want is to get updates on the progress of my download while waiting for download to complete.

The WebClient class has convenient DownloadProgressChanged event which seems like a perfect candidate to help me here, so I attached a delegate to it, but it was never called. Upon careful reading of the documentation, I learned that I must use the asynchronous version of the DownloadFile mehtod, DownloadFileAsync to receive any progress updates.

While I could switch my code around to accommodate new asynchronous calling pattern, I did not want to drastically redesign my existing code. It was already relying on the TPL for asynchrony, adding another asynchronous mechanism would add more complexity to my code. So I decided to try to keep the method that deals with WebClient synchronous, but still receive updates. To do this, I would need to call the DownloadFileAsync method, but then block my thread until download completes or fails. Luckily, WebClient has the DownloadFileCompleted event which would come in handy here. Since I would block my thread after calling the DownloadFileAsync method, when the DownloadFileCompleted fires, I would unblock my calling thread. Here’s the code that implements this solution


public void DownloadFile(Uri uri, string desintaion)
{
  using(var wc = new WebClient())
  {
    wc.DownloadProgressChanged += HandleDownloadProgress;
    wc.DownloadFileCOmpleted += HandleDownloadComplete;

    var syncObj = new Object();
    lock(syncObject)
    {
       wc.DownloadFileAsync(sourceUri, destination, syncObject);
       //This would block the thread until download completes
       Monitor.Wait(syncObject);
    }
  }
 
  //Do more stuff after download was complete
}

public void HandleDownloadComplete(object sender, AsyncCompletedEventArgs args)
{
   lock(e.UserState)
   {  
      //releases blocked thread
      Monitor.Pulse(e.UserState);
   }
}


public void HandleDownloadProgress(object sender, DownloadProgressChangedEventArgs args)
{
  //Process progress updates here
}

At the end, I still use WebClient synchronously without losing the benefits that come with its asynchronous usage.

Happy Downloading!

Advertisements

3 thoughts on “How to use .NET WebClient synchronously and still receive progress updates

  1. Tomasz Jagusz

    I was searching over the internet for proper WebClient usage. Everyone is just adding `using`block but You went one step further and You’ve added lock and monitor. Now this works as it should!
    Thanks for sharing.

    I have one question related to WebClient and Your code. I’d like to get real file name from Content-Disposition header before I start saving file. How can I do that before You call `wc.DownloadFileAsync`? Can WebClient get only headers?

    Reply
  2. Hel Thanatos

    public static void DownloadFile(Uri URL, string musicFilePath)
    {
    WebClient myWebClient = new WebClient();

    myWebClient.DownloadFileAsync(URL, musicFilePath);

    myWebClient.DownloadProgressChanged += DownloadProgressChanged;
    myWebClient.DownloadFileCompleted += DownloadFileCompleted;

    }

    public static void DownloadFileCompleted(object sender, AsyncCompletedEventArgs e)
    {
    downloadCompleted = true;
    downloadProgress = 0;
    //Do Stuff Here
    }

    public static void DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
    {
    downloadProgress = e.ProgressPercentage;
    //Console.WriteLine(“{0} downloaded {1} of {2} bytes. {3} % complete…”,
    // (string)e.UserState,
    // e.BytesReceived,
    // e.TotalBytesToReceive,
    // e.ProgressPercentage);
    }

    Could do that?

    Reply

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s