С# загружать асинхронно со списком очередей

Если вы не утруждаете себя чтением всего текста, вы можете перейти к двум последним точкам: p

Этот сайт помогал мне уже с десяток раз в прошлом, но сейчас мне самому очень нужна помощь.

Проблема заключается в следующем:

  • сначала я использовал функцию DownloadFile с опцией show UI. Это отлично сработало, но пользовательский интерфейс уродлив, и доступно не так много опций.

  • Затем я переключился на DownloadFileAsync с измененным событием прогресса, чтобы в основном иметь свой собственный пользовательский интерфейс. Единственная проблема, с которой я столкнулся, заключается в том, что я просматриваю список файлов, которые программа должна загрузить, и вызываю функцию загрузки (которая вызывает функцию DownloadAsync). Так:

    foreach (ListViewItem t in themeList.CheckedItems)
            {
                DownloadFile(file to be downloaded);
            }
    
  • Но, очевидно, это не сработало, так как функция DownloadFileAsync не поддерживает несколько одновременных вызовов, потому что в ней нет системы очередей, как у DownloadFile, поэтому она будет загружать только первый вызванный файл. Итак, что я сделал, так это сделал функцию, которая добавляет файл для загрузки в массив, выполняет цикл backgroundworker по списку и ждет с вызовом DownloadAsync, пока предыдущая загрузка не будет завершена. Это вроде получилось. Вот код:

    #region "Download functions"
    //Function that converts download speed to a nice user friendly format
    private static string BpsToString(double bps)
    {
        var m = new string[] { "bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB" };
        var i = 0;
        while (bps >= 0.9 * 1024)
        {
            bps /= 1024;
            i++;
        }
    
        return String.Format("{0:0.00} {1}/sec", bps, m[i]);
    }
    
    private bool _complete = false;
    private string _speed;
    private int _secondsRemaining = -1;
    private long _transferred = 0;
    private Stopwatch _sw = new Stopwatch();
    private List<string[]> _fd = new List<string[]>();
    private void DownloadFile(string url, string des, bool overwrite = false)
    {
        if (overwrite) //if the file needs to be overwritten or not
        {
            if (File.Exists(des)) File.Delete(des);
        }
        else
        {
            if (File.Exists(des)) return;
        }
    
        if (!Directory.Exists(Path.GetDirectoryName(des))) //create the directory if it doesn't exist
            Directory.CreateDirectory(Path.GetDirectoryName(des));
    
        string[] file = {url, des};
        _fd.Add(file); //add file to queue list
    
        if(!backgroundDownloader.IsBusy) //if downloader isn't doing anything, start it again
            backgroundDownloader.RunWorkerAsync();
    }
    
    //function called by the backgroundworker to actually download the file
    private void ContinueDownloadFile(string url, string des)
    {
        var webClient = new WebClient();
        webClient.DownloadFileCompleted += new AsyncCompletedEventHandler(Completed);
        webClient.DownloadProgressChanged += new DownloadProgressChangedEventHandler(ProgressChanged);
        webClient.DownloadFileAsync(new Uri(_fd[0][0]), _fd[0][1]);
    }
    
    //when download completed, set progress bar to 0% and remove the first (0) download from the queue
    private void Completed(object sender, AsyncCompletedEventArgs e)
    {
        SetProgressText("Idle");
        SetProgressValue(0);
    
        if(_fd.Count != 0)
            _fd.RemoveAt(0);
    
        _complete = true; //if it's complete, set to true so the backgroundworker knows it can start the next download
    }
    
    //progress bar value change and status change for download speed etc...
    private void ProgressChanged(object sender, DownloadProgressChangedEventArgs e)
    {
        if(progressLabel.Text == "Idle")
            SetProgressText("Downloading...");
    
        if (_sw.Elapsed >= TimeSpan.FromSeconds(1))
        {
            _sw.Stop();
    
            var bytes = e.BytesReceived - _transferred;
            var bps = bytes * 1000.0 / _sw.Elapsed.TotalMilliseconds;
            _speed = BpsToString(bps);
    
            _secondsRemaining = (int)((e.TotalBytesToReceive - e.BytesReceived) / bps);
    
            _transferred = e.BytesReceived;
            _sw.Reset();
            _sw.Start();
    
            SetProgressText("Downloading: " + e.ProgressPercentage + "% | Seconds remaining: " +
            _secondsRemaining + " | Files remaining: " + _fd.Count + " | Speed: " + _speed);
        }
    
        SetProgressValue(e.ProgressPercentage);
    }
    
    //the backgroundworker who starts the downloads from the list one by one
    private void BackgroundDownloaderDoWork(object sender, DoWorkEventArgs e)
    {
        while (_fd.Count != 0)
        {
            _sw.Start();
            _complete = false; //let the backgroundworker wait till the download is complete
            ContinueDownloadFile(_fd[0][0], _fd[0][1]);
    
            while(!_complete) //let it wait here
                Thread.Sleep(100);
    
            _sw.Stop();
            _sw.Reset();
        }
    }
    
    #endregion
    
  • Итак, в основном моя следующая проблема заключается в том, что программа должна ждать с выполнением любого другого кода, пока загрузка не будет завершена. Я сделал это, выполнив:

    while (_fd.Count != 0)
            Application.DoEvents();
    
  • Это, очевидно, не лучшее решение, так как они могут щелкать другие вещи, пока загрузки заняты, но да, Thread.Sleep просто заморозит все. Вместо этого я бы сделал форму ожидания (может быть, здесь индикатор выполнения вместо основной формы) с фокусом на ней поверх основной формы, поэтому они не могут щелкнуть основную форму и поместить Thread.Sleep в основную форму ?

  • Как бы вы решили это? Вы бы также использовали фоновый рабочий, который перебирает массив файлов, или есть более простой и эффективный способ. Может быть, не с помощью DownloadFileAsync, а с ручной загрузкой сокетов?

  • В основном я хочу загружать файлы синхронно, но иметь свой собственный пользовательский интерфейс (поэтому мне нужно использовать функции асинхронной загрузки). Ха-ха

Надеюсь, я достаточно вас проинформировал. Заранее спасибо.


person d0ggy    schedule 10.09.2011    source источник
comment
Может быть, это публикация TL; DR?   -  person Uwe Keim    schedule 10.09.2011
comment
Я сделаю его короче, так как он действительно содержит некоторые ненужные вещи.   -  person d0ggy    schedule 10.09.2011
comment
Ваш план по пуле № 5 звучит как лучший способ. Вы также можете попробовать использовать библиотеку параллельных задач (TPL). Это упростило бы одновременные вызовы загрузки, и вместе с этим вы могли бы использовать синхронный загрузчик файлов.   -  person Brent M. Spell    schedule 10.09.2011


Ответы (1)


Используйте модальное диалоговое окно, возможно, добавьте индикатор выполнения для визуального удовлетворения, информируя пользователя о том, что процесс работает.

Таким образом, вы можете выполнять асинхронную загрузку, не позволяя взаимодействовать с элементами управления основной формы.

person IAbstract    schedule 10.09.2011
comment
Спасибо, посмотрю модальный диалог. - person d0ggy; 10.09.2011
comment
Кажется, я не могу закрыть модальное диалоговое окно, чтобы выполнить дальнейший код. Я попытался установить таймер в форме ожидания, который устанавливает для DialogResult значение OK, когда индикатор выполнения достигает 100, но он не закрывается. Пробовал из фонового рабочего на главной форме, но он не закрывается. Хм.. - person d0ggy; 10.09.2011
comment
Когда вы начнете загрузку, отобразите модальное диалоговое окно. В вашем асинхронном обратном вызове вы должны избавиться от диалога. - person IAbstract; 10.09.2011
comment
Спасибо. После того, как я вызвал DownloadFileAsync, я показываю диалоговое окно. Он отлично загружает ПЕРВЫЙ файл и закрывается. Затем он открывает диалоговое окно во второй раз (второй файл), но он просто сидит там (значение бездействия и индикатора выполнения равно 0). Я делаю _wait = new Wait, а затем вызываю ShowDialog(this) сразу после DownloadFileAsync. - person d0ggy; 10.09.2011