Implement WebClient’s asynchronous download with cancellation capability

Standard
Share

When I wrote my article regarding use of the BackgroundWorker to keep the UI responsive, I used the WebClient’s DownloadString method as an example of a long-running process. I used this as an example in explaining how to use the BackgroundWorker, which simply allows for thread-blocking code to be run in a separate thread.

As it turns out, this is a terrible idea. Not the whole thread-blocking code in a separate thread, but the use of a WebClient in a BackgroundWorker. You see, the WebClient class implements a DownloadStringAsync() method. This allows the calling code to continue to run, and the asynchronous process will eventually return a string. But since it’s asynchronous, our UI thread will never get blocked by a long-running process. And since it too supports asynchronous cancellation, there’s no reason to embed it inside a BackgroundWorker.

The implementation of the WebClient with DownloadStringAsync and cancellation is almost identical to that of the configuration of the BackgroundWorker. If you’ve read that article, this will seem a little like déjà vu.

namespace jkshay.Examples
{
    public class WebClientDownloadAsyncExample : ViewModelBase
    {
        public WebClient client { get; set; }
    
        public string JSONString { get; set; }
        
        public WebClientDownloadAsyncExample()
        {
            client = new WebClient();
            client.DownloadStringCompleted += new DownloadStringCompletedEventHandler (DownloadStringCallback);
            client.DownloadStringAsync();
        }

        public void DownloadStringCallback(object sender, DownloadStringCompletedEventArgs e)
        {
            if(e.Cancelled && e.Error == null)
            {
                JSONString = String.Empty;
                MessageBox.Show("Data download was cancelled");
            }
            else
            {
                JSONString = (string)e.Result;
            }
        }

        private RelayCommand cancelDownloadStringCommand;
        public ICommand CancelDownloadStringCommand
        {
            get
            {
                if(cancelDownloadStringCommand == null)
                {
                    cancelDownloadStringCommand = new RelayCommand(param => CancelDownloadString());
                }
                return cancelDownloadStringCommand;
            }
        }

        public void CancelDownloadString()
        {
            if(client != null)
                client.CancelAsync();
        }
    }
}

Let’s go over this code in detail. First, note that in line #3 we define our class and derive it from ViewModelBase. This is a class I use that provides CommandBinding via the RelayCommand as well as an implementation of NotifyPropertyChanged. It is the RelayCommand it will provide for us in this example. For more information on ViewModelBase and the MVVM architecture, read my article on that subject.

Line #5 defines an automatic property of type WebClient. By defining a property on the class and storing our WebClient here, we have access to the WebClient outside the method in which it was instantiated, allowing us to cancel the download from a different method.

Lines #9-14 define the constructor of the WebClientDownloadAsyncExample class. Line #11 sets the “client” property value to that of a new instance of the WebClient class. Line #12 defines the DownloadStringCompleted event handler as a method we call DownloadStringCallback. Line #13 starts the asynchronous string download process.

Lines #16-27 define the DownloadStringCompleted event handler. This event fires when the asynchronous download string process throws an exception, is cancelled, or completes. The DownloadStringCompletedEventArgs parameter houses any error or cancellation data, as well as the downloaded string if the process were allowed to complete.

In line #18, we check if the WebClient was cancelled (e.Cancelled is set to true when the client.CancelAsync method is called) and that an exception was not thrown (e.Error == null). If so, we set our JSONString property to an empty string and produce a message box stating that the download was cancelled. Otherwise, line #25 sets JSONString to the value of e.Result, cast to a string.

This gives us an asynchronous download, allowing us to keep our UI responsive. But how do we cancel a download? Lines #29-46 give us this functionality.

In lines #29-40, we’re simply defining a RelayCommand called CancelDownloadStringCommand that, when invoked, will run the CancelDownloadString method. You’ll see in the CancelDownloadstring method (lines #42-46) that we simply check to see if client is not null, then calling the client’s CancelAsync method if it is not null. For more information on CommandBinding and how to use it, see my post on that subject.

In conclusion, the use of a WebClient in a BackgroundWorker is not necessary, and is overkill. The WebClient

  1. offers an asynchronous string download method (DownloadStringAsync),
  2. offers the ability to cancel the download, and
  3. fires an event when the download is completed, giving us access to thrown exception information and cancelled download information, as well as the string downloaded.

Leave a Reply

Your email address will not be published. Required fields are marked *