Разрабатываемая программа должна уметь асинхронно скачивать до трех картинок включительно. Все работает на WebClient. Не понимаю, как сделать так, чтобы текущее значение прогресса в прогрессбаре подстраивалось под текущее количество закачек.
Я как то делал для себя нечто подобное, код как некий черновик, так что часть косяков мог и не заметить.
DownloadManager
.В него добавляем необходимые свойства. Я для удобства добавлю коллекцию ссылок, текущий размер и общий размер:
public List<string> DownloadLinks { get; } = new List<string>();
public long CurrentSize { get; private set; }
public long TotalSize { get; private set; }
Далее реализуем события:
Создадим класс, который будет содержать в себе информацию об изменении прогрессии загрузки всех файлов. Я назвал его просто, DownloadManagerEventArgs
:
class DownloadManagerEventArgs : EventArgs
{
public DownloadManagerEventArgs(long current, long total)
{
CurrentSize = current;
TotalSize = total;
}
public long CurrentSize { get; }
public long TotalSize { get; }
public double Percent => 100 * CurrentSize / TotalSize;
}
Далее добавим в класс DownloadManager
два события. Первое произойдет при изменении прогресса, а второе при окончании загрузки:
public event EventHandler<DownloadManagerEventArgs> OnDownload;
public event Action OnDownloadCompleated;
Теперь реализуем метод, который добавит ссылку в коллекцию. Я для удобства реализую два метода (один для добавления одной ссылки, а другой для целой коллекции), также я напишу их по принципу так называемых "Цепочных методов":
public DownloadManager Add(string link)
{
DownloadLinks.Add(link);
return this;
}
public DownloadManager Add(string[] links)
{
DownloadLinks.AddRange(links);
return this;
}
Также нам нужен метод, который посчитает общий размер загружаемых файлов. Сделать это можно путем чтения заголовка Content-Length
. К сожалению метод не 100%, но хоть что то:
private async Task<long> GetTotalSizeTask()
{
long result = 0;
foreach(var link in DownloadLinks)
{
var req = WebRequest.Create(link);
req.Method = "HEAD";
using(WebResponse resp = await req.GetResponseAsync())
{
if(long.TryParse(resp.Headers.Get("Content-Length"), out long ContentLength))
result += ContentLength;
}
}
return result;
}
Также я лично для себя набросал небольшой метод, который из ссылки возьмем имя файла с его расширением. Вам наверно нужно будет что то другое придумать.:
private string GetFileNameFromUrl(string url)
{
Uri uri = new Uri(url);
return Path.GetFileName(uri.LocalPath);
}
Хорошо, теперь самое главное. Нам нужна задача (Task
), которая возьмет ссылку и путь и с этими данными начнет загрузку файла:
private async Task DownloadTask(string url, string path)
{
using(var client = new WebClient())
{
long prev = 0;
client.DownloadProgressChanged += (s, a) =>
{
lock(this)
{
if(a.BytesReceived <= prev) return;
var diff = a.BytesReceived - prev;
CurrentSize += diff;
prev = a.BytesReceived;
OnDownload?.Invoke(this, new DownloadManagerEventArgs(CurrentSize, TotalSize));
}
};
await client.DownloadFileTaskAsync(url, path);
}
}
Тут мы используем WebClient()
, у которого подписываемся на событие DownloadProgressChanged
и реализуем его внутри Task
. Внутри обработчика события мы отсекаем все отрицательные значения загруженных байтов, высчитываем сколько именно мы скачали от текущего файла байт (прибавляя это значение нашему свойству CurrentSize
), ну и вызываем событие с нужными нам данными. До такой реализации я дошел благодаря этому ответу.
Имея задачу загрузки файла, мы можем смело делать главный метод загрузки всех файлов:
public async Task Download()
{
TotalSize = await GetTotalSizeTask();
await Task.WhenAll(DownloadLinks.Select(x => DownloadTask(x, GetFileNameFromUrl(x))).ToArray());
OnDownloadCompleated?.Invoke();
}
Тут особо пояснять нечего, просто вызываем GetTotalSizeTask()
, который посчитает нам общий размер всех файлов и занесет в свойство. Дальше мы преобразуем все string
ссылки в Task
, которые с помощью Task.WhenAll()
будем ожидать полного завершения. В конце всех действий мы оповестим кого надо с помощью события OnDownloadCompleated
.
Вот собственно и все. Мы сделали класс, который отвечает полностью за загрузку неограниченного числа файлов, асинхронно. Давайте использовать его:
var manager = new DownloadManager();
manager.OnDownload += Manager_OnDownload;
manager.OnDownloadCompleated += () => MessageBox.Show("Загрузка завершена!");
await manager.Add("https://images.wallpaperscraft.ru/image/peshchera_temnyj_ushchele_150108_1920x1080.jpg")
.Add(new[]
{
"https://images.wallpaperscraft.ru/image/tkan_tekstura_belyj_150101_5472x3648.jpg",
"https://images.wallpaperscraft.ru/image/chashka_kofe_ruka_150110_4642x2922.jpg",
"https://images.wallpaperscraft.ru/image/fraktal_plamennyj_iarkij_150047_3200x2134.jpg"
})
.Add("https://speed.hetzner.de/100MB.bin")
.Download();
Ну и обработчик простенький сделаем, который просто выведет результат в ProgressBar
и в окно отладки:
private void Manager_OnDownload(object sender, DownloadManagerEventArgs e)
{
progressBar1.Value = (int)e.Percent;
Debug.WriteLine($"{e.CurrentSize}/{e.TotalSize} [{e.Percent}%]");
}
Айфон мало держит заряд, разбираемся с проблемой вместе с AppLab
помогите разобратьсяМне нужно, чтобы объект двигался в то место куда было осуществлено нажатие на экран, вместо этого объект летит куда ему...
Подключаюсь из C# через comconnector к 1С базе и создаю новый ПриходныйКассовыйОрдер и вроде бы все отлично, но вот номер документа присваеваемый...
Как это реализовать? Приложил скрипт, который сейчас рандомно генерирует блоки БЕЗ препятствий
Мне надо сделать progress bar в EditorWindowДля этого я выполняю функцию расчётов в потоках: Thread thread = new Thread(_worker