Latheesan
Latheesan

Reputation: 24116

Download & extract zip file using Async / Await with progress reporting

I want to implement a re-usable class that will facilitate with downloading a given zip file and extract it, whilst reporting progress using the C# 5 await/async feature.

I am new to this and trying to wrap my head around it at the moment. This is my Installer class so far:

class Installer
{
    Button saveButton;
    ProgressBar progressBar;
    Label statusLabel;
    Boolean downloadDone;

    public Installer(Button _saveButton, ProgressBar _progressBar, Label _statusLabel)
    {
        saveButton = _saveButton;
        progressBar = _progressBar;
        statusLabel = _statusLabel;
    }

    public async void Start(string fileUrl)
    {
        saveButton.BeginInvoke((Action)(() => {
            saveButton.Enabled = false;
        }));

        Task<bool> downloadArchiveTask = DownloadArchiveAsync(fileUrl);

        bool downloadArchiveDone = await downloadArchiveTask;

        if (downloadArchiveDone)
            statusLabel.BeginInvoke((Action)(() => {
                statusLabel.Text = "Download Completed";
            }));
    }

    async Task<bool> DownloadArchiveAsync(string fileUrl)
    {
        var downloadLink = new Uri(fileUrl);
        var saveFilename = Path.GetFileName(downloadLink.AbsolutePath);

        DownloadProgressChangedEventHandler DownloadProgressChangedEvent = (s, e) =>
        {
            progressBar.BeginInvoke((Action)(() => {
                progressBar.Value = e.ProgressPercentage;
            }));

            var downloadProgress = string.Format("{0} MB / {1} MB",
                    (e.BytesReceived / 1024d / 1024d).ToString("0.00"),
                    (e.TotalBytesToReceive / 1024d / 1024d).ToString("0.00"));

            statusLabel.BeginInvoke((Action)(() => {
                statusLabel.Text = "Downloading " + downloadProgress + " ...";
            }));
        };

        AsyncCompletedEventHandler AsyncCompletedEvent = (s, e) =>
        {
            // Todo: Extract
            downloadDone = true;
        };

        using (WebClient webClient = new WebClient())
        {
            webClient.DownloadProgressChanged += DownloadProgressChangedEvent;
            webClient.DownloadFileCompleted += AsyncCompletedEvent;
            webClient.DownloadFileAsync(downloadLink, saveFilename);
        }

        while (!downloadDone) ;

        return true;
    }
}

This is how I use it:

(new Installer(startBtn, progressBar, statusLabel)).Start("http://nginx.org/download/nginx-1.9.4.zip");

I am not entirely sure if I have implemented this correctly. Visual studio is giving me the following warning:

DownloadArchiveAsync - This async method lacks 'await' operators and will run synchronously.

Also note; I currently have no proper way of detecting when the download is completed, so I am using bool and a while loop - not sure if this is recommended also.

What is the correct way to use async/await to asynchronously download a zip file and report progress?


EDIT

After digging around, I found one possible solution to this. I've implemented this method:

async Task IsDownloadDone()
{
    await Task.Run(() =>
    {
        while (!downloadDone) ;
    });
}

and updated the DownloadArchiveAsync return like this:

await IsDownloadDone();
return true;

Complete code now looks like this: http://pastebin.com/MuW0386K

Is this the correct way to implement this?

Upvotes: 1

Views: 2646

Answers (1)

Timbo
Timbo

Reputation: 28050

You can replace DownloadArchiveAsync with something like this, and it will actually work asynchronously:

async Task<bool> DownloadArchiveAsync( string fileUrl )
{
    var downloadLink = new Uri( fileUrl );
    var saveFilename = System.IO.Path.GetFileName( downloadLink.AbsolutePath );

    DownloadProgressChangedEventHandler DownloadProgressChangedEvent = ( s, e ) =>
    {
        progressBar.BeginInvoke( (Action)(() =>
        {
            progressBar.Value = e.ProgressPercentage;
        }) );

        var downloadProgress = string.Format( "{0} MB / {1} MB",
                (e.BytesReceived / 1024d / 1024d).ToString( "0.00" ),
                (e.TotalBytesToReceive / 1024d / 1024d).ToString( "0.00" ) );

        statusLabel.BeginInvoke( (Action)(() =>
        {
            statusLabel.Text = "Downloading " + downloadProgress + " ...";
        }) );
    };

    using (WebClient webClient = new WebClient())
    {
        webClient.DownloadProgressChanged += DownloadProgressChangedEvent;
        await webClient.DownloadFileTaskAsync( downloadLink, saveFilename );
    }

    return true;
}

Edit: I found DownloadFileTaskAsync in the MSDN, makes things a lot prettier.

async on the method means this method uses await. So use the awaitable function in 'WebClient'.

Upvotes: 3

Related Questions