Как отслеживать ход загрузки асинхронного файла в хранилище Azure
Есть ли способ отслеживать ход загрузки файла в контейнер хранения Azure?
Я пытаюсь сделать консольное приложение для загрузки данных в Azure с использованием C#.
Мой кул на данный момент выглядит так:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Auth;
using Microsoft.WindowsAzure.Storage.Blob;
using System.Configuration;
using System.IO;
using System.Threading;
namespace AdoAzure
{
class Program
{
static void Main(string[] args)
{
CloudStorageAccount storageAccount = CloudStorageAccount.Parse(
ConfigurationManager.ConnectionStrings["StorageConnectionString"].ConnectionString);
CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();
CloudBlobContainer container = blobClient.GetContainerReference("adokontajnerneki");
container.CreateIfNotExists();
CloudBlobClient myBlobClient = storageAccount.CreateCloudBlobClient();
CloudBlockBlob myBlob = container.GetBlockBlobReference("racuni.adt");
CancellationToken ca = new CancellationToken();
var ado = myBlob.UploadFromFileAsync(@"c:\bo\racuni.adt", FileMode.Open, ca);
Console.WriteLine(ado.Status); //Does Not Help Much
ado.ContinueWith(t =>
{
Console.WriteLine("It is over"); //this is working OK
});
Console.WriteLine(ado.Status); //Does Not Help Much
Console.WriteLine("theEnd");
Console.ReadKey();
}
}
}
Этот кусок кода работает хорошо, но мне бы хотелось иметь какой-то индикатор прогресса, чтобы пользователи могли видеть, что есть задачи. Есть ли что-то встроенное в пространство имен WindowsAzure.Storage.Blob, чтобы я мог использовать его как кролика из шляпы?
5 ответов
Я не думаю, что это возможно, потому что выгрузка файла - это отдельная задача, и хотя внутри файл разбит на несколько частей и эти фрагменты загружаются, код фактически ожидает завершения всей задачи.
Один из вариантов - вручную разбить файл на куски и загрузить эти куски асинхронно, используя PutBlockAsync
метод. После загрузки всех чанков вы можете позвонить PutBlockListAsync
способ совершить блоб. Пожалуйста, смотрите код ниже, который выполняет это:
using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Auth;
using Microsoft.WindowsAzure.Storage.Blob;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApplication1
{
class Program
{
static CloudStorageAccount storageAccount = new CloudStorageAccount(new StorageCredentials("accountname", "accountkey"), true);
static void Main(string[] args)
{
CloudBlobClient myBlobClient = storageAccount.CreateCloudBlobClient();
myBlobClient.SingleBlobUploadThresholdInBytes = 1024 * 1024;
CloudBlobContainer container = myBlobClient.GetContainerReference("adokontajnerneki");
//container.CreateIfNotExists();
CloudBlockBlob myBlob = container.GetBlockBlobReference("cfx.zip");
var blockSize = 256 * 1024;
myBlob.StreamWriteSizeInBytes = blockSize;
var fileName = @"D:\cfx.zip";
long bytesToUpload = (new FileInfo(fileName)).Length;
long fileSize = bytesToUpload;
if (bytesToUpload < blockSize)
{
CancellationToken ca = new CancellationToken();
var ado = myBlob.UploadFromFileAsync(fileName, FileMode.Open, ca);
Console.WriteLine(ado.Status); //Does Not Help Much
ado.ContinueWith(t =>
{
Console.WriteLine("Status = " + t.Status);
Console.WriteLine("It is over"); //this is working OK
});
}
else
{
List<string> blockIds = new List<string>();
int index = 1;
long startPosition = 0;
long bytesUploaded = 0;
do
{
var bytesToRead = Math.Min(blockSize, bytesToUpload);
var blobContents = new byte[bytesToRead];
using (FileStream fs = new FileStream(fileName, FileMode.Open))
{
fs.Position = startPosition;
fs.Read(blobContents, 0, (int)bytesToRead);
}
ManualResetEvent mre = new ManualResetEvent(false);
var blockId = Convert.ToBase64String(Encoding.UTF8.GetBytes(index.ToString("d6")));
Console.WriteLine("Now uploading block # " + index.ToString("d6"));
blockIds.Add(blockId);
var ado = myBlob.PutBlockAsync(blockId, new MemoryStream(blobContents), null);
ado.ContinueWith(t =>
{
bytesUploaded += bytesToRead;
bytesToUpload -= bytesToRead;
startPosition += bytesToRead;
index++;
double percentComplete = (double)bytesUploaded / (double)fileSize;
Console.WriteLine("Percent complete = " + percentComplete.ToString("P"));
mre.Set();
});
mre.WaitOne();
}
while (bytesToUpload > 0);
Console.WriteLine("Now committing block list");
var pbl = myBlob.PutBlockListAsync(blockIds);
pbl.ContinueWith(t =>
{
Console.WriteLine("Blob uploaded completely.");
});
}
Console.ReadKey();
}
}
}
Решение Гаурава работает хорошо и очень похоже на http://blogs.msdn.com/b/kwill/archive/2011/05/30/asynchronous-parallel-block-blob-transfers-with-progress-change-notification.aspx, Проблема с этим кодом заключается в том, что вы выполняете много сложной работы с минимальной обработкой ошибок. Я не говорю, что с кодом Гаурава что-то не так - он выглядит солидно, - но особенно с кодом связи, связанным с сетью, существует множество переменных и множество проблем, которые вы должны учитывать.
По этой причине я изменил свой исходный блог, чтобы использовать код загрузки из клиентской библиотеки хранилища (исходя из предположения, что код от команды хранилища Azure был более надежным, чем все, что я мог написать), и отслеживал прогресс с помощью класса ProgressStream. Вы можете увидеть обновленный код на http://blogs.msdn.com/b/kwill/archive/2013/03/06/asynchronous-parallel-block-blob-transfers-with-progress-change-notification-2-0.aspx.
Как насчет этого.
public class ObservableFileStream : FileStream
{
private Action<long> _callback;
public ObservableFileStream(String fileName, FileMode mode, Action<long> callback) : base(fileName, mode)
{
_callback = callback;
}
public override void Write(byte[] array, int offset, int count)
{
_callback?.Invoke(Length);
base.Write(array, offset, count);
}
public override int Read(byte[] array, int offset, int count)
{
_callback?.Invoke(Position);
return base.Read(array, offset, count);
}
}
public class Test
{
private async void Upload(String filePath, CloudBlockBlob blob)
{
ObservableFileStream fs = null;
using (fs = new ObservableFileStream(filePath, FileMode.Open, (current) =>
{
Console.WriteLine("Uploading " + ((double)current / (double)fs.Length) * 100d);
}))
{
await blob.UploadFromStreamAsync(fs);
}
}
}
В приведенном ниже коде используется пакет SDK хранилища BLOB-объектов Azure версии 12. Ссылка для справки: https://www.craftedforeveryone.com/upload-or-download-file-from-azure-blob-storage-with-progress-percentage-csharp/
public void UploadBlob(string fileToUploadPath)
{
var file = new FileInfo(fileToUploadPath);
uploadFileSize = file.Length; //Get the file size. This is need to calculate the file upload progress
//Initialize a progress handler. When the file is being uploaded, the current uploaded bytes will be published back to us using this progress handler by the Blob Storage Service
var progressHandler = new Progress();
progressHandler.ProgressChanged += UploadProgressChanged;
var blob = new BlobClient(connectionString, containerName, file.Name); //Initialize the blob client
blob.Upload(fileToUploadPath, progressHandler: progressHandler); //Make sure to pass the progress handler here
}
private void UploadProgressChanged(object sender, long bytesUploaded)
{
//Calculate the progress and update the progress bar.
//Note: the bytes uploaded published back to us is in long. In order to calculate the percentage, the value has to be converted to double.
//Auto type casting from long to double happens here as part of function call
Console.WriteLine(GetProgressPercentage(uploadFileSize, bytesUploaded));
}
private double GetProgressPercentage(double totalSize,double currentSize)
{
return (currentSize / totalSize) * 100;
}
Использовать
BlockBlobClient
вместо
BlobClient
(
using Azure.Storage.Blobs.Specialized;
)
private const string StorageConnectionString = "<Your storage connection string>";
private const string StorageContainerName = "<Your storage container name>";
[HttpPost]
public async Task<IActionResult> UploadFiles(IList<IFormFile> files)
{
foreach (var file in files)
{
var blockBlobClient = new BlockBlobClient(StorageConnectionString, StorageContainerName, file.FileName); // Add Guid to FileName to ensure uniqueness
using var output = blockBlobClient.OpenWrite(overwrite: true);
using var input = file.OpenReadStream();
var buffer = new byte[64 * 1024];
var totalReadBytes = 0L;
var readBytes = 0;
while ((readBytes = input.Read(buffer, 0, buffer.Length)) > 0)
{
await output.WriteAsync(buffer, 0, readBytes);
totalReadBytes += readBytes;
//progress = Math.Round((totalReadBytes * 1d / sessionUploadFileInfo.FileLength * 100d), 2, MidpointRounding.AwayFromZero);
//do what you do with the progress, save it in session or however you display it to the user...
//With WebAPI, I use a static dictionary and a client Id to store file upload progress
//A separate REST request with client Id gets the file progress' from the dictionary
}
}
return Content("success");
}