C# прогресс нескольких загрузок

389
06 октября 2021, 20:20

Разрабатываемая программа должна уметь асинхронно скачивать до трех картинок включительно. Все работает на WebClient. Не понимаю, как сделать так, чтобы текущее значение прогресса в прогрессбаре подстраивалось под текущее количество закачек.

Answer 1

Я как то делал для себя нечто подобное, код как некий черновик, так что часть косяков мог и не заметить.

  • Создадим новый класс, назовем его к примеру 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}%]");
}

READ ALSO
Движение объекта Unity

Движение объекта Unity

помогите разобратьсяМне нужно, чтобы объект двигался в то место куда было осуществлено нажатие на экран, вместо этого объект летит куда ему...

98
1C Comconnector - Номер документа

1C Comconnector - Номер документа

Подключаюсь из C# через comconnector к 1С базе и создаю новый ПриходныйКассовыйОрдер и вроде бы все отлично, но вот номер документа присваеваемый...

96
Последовательный спаун блоков пути и рандомный спаун видов препятствий внутри них

Последовательный спаун блоков пути и рандомный спаун видов препятствий внутри них

Как это реализовать? Приложил скрипт, который сейчас рандомно генерирует блоки БЕЗ препятствий

154
Как сделать progress bar в EditorWindow с Threading в Unity?

Как сделать progress bar в EditorWindow с Threading в Unity?

Мне надо сделать progress bar в EditorWindowДля этого я выполняю функцию расчётов в потоках: Thread thread = new Thread(_worker

198