Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

azure blob storage async download with progress bar

i am trying to get a COMPLETE example of downloading a file form Azure Blob Storage using the .DownloadToStreamAsync() method wired up to a progress bar.

i've found references to old implementations of the azure storage sdk, but they dont compile with the newer sdk (that has implemented these async methods) or don't work with current nuget packages.

https://blogs.msdn.microsoft.com/avkashchauhan/2010/11/03/uploading-a-blob-to-azure-storage-with-progress-bar-and-variable-upload-block-size/

https://blogs.msdn.microsoft.com/kwill/2013/03/05/asynchronous-parallel-blob-transfers-with-progress-change-notification-2-0/

i'm a newbie to async/await threading in .NET, and was wondering if someone could help me out with taking the below (in a windows form app) and showing how i can 'hook' into the progress of the file download... i see some examples dont use the .DownloadToStream method, and they instead download chunks of the blob file.. but i wondered since these new ...Async() methods exist in the newer Storage SDK's, if there was something smarter that could be done?

So assuming the below is working (non async), what additionally would i have to do to use the blockBlob.DownloadToStreamAsync(fileStream); method, is this even the right use of this, and how can i get the progress?

ideally i am after any way i can just hook the progress of the blob download so i can update a Windows Form UI on big downloads.. so if the below is not the right way, please enlighten me :)

// Retrieve storage account from connection string.
CloudStorageAccount storageAccount = CloudStorageAccount.Parse(
    CloudConfigurationManager.GetSetting("StorageConnectionString"));

// Create the blob client.
CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();

// Retrieve reference to a previously created container.
CloudBlobContainer container = blobClient.GetContainerReference("mycontainer");

// Retrieve reference to a blob named "photo1.jpg".
CloudBlockBlob blockBlob = container.GetBlockBlobReference("photo1.jpg");

// Save blob contents to a file.
using (var fileStream = System.IO.File.OpenWrite(@"path\myfile"))
{
    blockBlob.DownloadToStream(fileStream);
}

Using the awesome suggested method (downloading 1mb chunks) kindly suggsted by Gaurav, i have implemented using a background worker to do the download so i can update the UI as i go.

The main part inside the do loop that downloads the range to a stream and then writes the stream to the file system I havent touched from the original example, but i have added code to update the worker progress and to listen for the worker cancellation (to abort the download).. not sure if this could be the issue?

For completeness, below is everything inside the worker_DoWork method:

public void worker_DoWork(object sender, DoWorkEventArgs e)
    {
        object[] parameters = e.Argument as object[];
        string localFile = (string)parameters[0];
        string blobName = (string)parameters[1];
        string blobContainerName = (string)parameters[2];
        CloudBlobClient client = (CloudBlobClient)parameters[3];      

        try
        {
            int segmentSize = 1 * 1024 * 1024; //1 MB chunk
            var blobContainer = client.GetContainerReference(blobContainerName);
            var blob = blobContainer.GetBlockBlobReference(blobName);
            blob.FetchAttributes();
            blobLengthRemaining = blob.Properties.Length;
            blobLength = blob.Properties.Length;
            long startPosition = 0;
            do
            {
                long blockSize = Math.Min(segmentSize, blobLengthRemaining);
                byte[] blobContents = new byte[blockSize];
                using (MemoryStream ms = new MemoryStream())
                {
                    blob.DownloadRangeToStream(ms, startPosition, blockSize);
                    ms.Position = 0;
                    ms.Read(blobContents, 0, blobContents.Length);
                    using (FileStream fs = new FileStream(localFile, FileMode.OpenOrCreate))
                    {
                        fs.Position = startPosition;
                        fs.Write(blobContents, 0, blobContents.Length);
                    }
                }
                startPosition += blockSize;
                blobLengthRemaining -= blockSize;

                if (blobLength > 0)
                {
                    decimal totalSize = Convert.ToDecimal(blobLength);
                    decimal downloaded = totalSize - Convert.ToDecimal(blobLengthRemaining);
                    decimal blobPercent = (downloaded / totalSize) * 100;
                    worker.ReportProgress(Convert.ToInt32(blobPercent));
                }

                if (worker.CancellationPending)
                {
                    e.Cancel = true;
                    blobDownloadCancelled = true;
                    return;
                }
            }
            while (blobLengthRemaining > 0);
        }
        catch (Exception ex)
        {
            MessageBox.Show(ex.Message);
        }
    }

This is working, but on bigger files (30mb for example), i sometimes am getting 'can't write to file as open in another process error...' and the process fails..

like image 942
Paul O'Brien Avatar asked Oct 12 '16 03:10

Paul O'Brien


1 Answers

Using your code:

using (var fileStream = System.IO.File.OpenWrite(@"path\myfile"))
{
    blockBlob.DownloadToStream(fileStream);
}

It is not possible to show the progress because the code comes out of this function only when the download is complete. DownloadToStream function will internally split a large blob in chunks and download the chunks.

What you need to do is download these chunks using your code. What you have to do is use DownloadRangeToStream method. I answered a similar question some time back that you may find useful: Azure download blob part.

like image 87
Gaurav Mantri Avatar answered Oct 10 '22 06:10

Gaurav Mantri