Domanda

Se non ti preoccupi di leggere l'intero testo, puoi saltare agli ultimi due punti: p

Questo sito mi ha aiutato una dozzina di volte già in passato, ma ora ho davvero bisogno di aiuto da solo.

Il problema è il seguente:

  • prima ho usato la funzione DownloadFile con l'opzione show UI. Funzionava benissimo, ma l'interfaccia utente è brutta e non sono disponibili molte opzioni.

  • Sono quindi passato a DownloadFileAsync con l'evento di avanzamento modificato per avere sostanzialmente la mia interfaccia utente. L'unico problema che ho avuto è stato scorrere un elenco di file che il programma deve scaricare e chiamare la funzione di download (che chiama la funzione DownloadAsync). In questo modo:

    foreach (ListViewItem t in themeList.CheckedItems)
            {
                DownloadFile(file to be downloaded);
            }
    
  • Ma ovviamente questo non ha funzionato, poiché la funzione DownloadFileAsync non supporta più chiamate contemporaneamente perché non esiste un sistema di code come DownloadFile, quindi scaricherà solo il primo file chiamato. Quindi quello che ho fatto è creare una funzione che aggiunge il file da scaricare a un array e ha un ciclo in backgroundworker attraverso l'elenco e attendere chiamando DownloadAsync fino al completamento del download precedente. Questo ha funzionato. Ecco il codice:

    #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
    
  • Quindi, fondamentalmente, il mio prossimo problema è che il programma deve attendere con l'esecuzione di altro codice finché i download non sono terminati. L'ho fatto in questo modo:

    while (_fd.Count != 0)
            Application.DoEvents();
    
  • Questa non è ovviamente la soluzione migliore in quanto possono fare clic su altre cose mentre i download sono occupati, ma sì, Thread.Sleep bloccherebbe tutto. Invece creerei un modulo di attesa (forse qui una barra di avanzamento invece che sul modulo principale) con il focus su di esso sopra il modulo principale, in modo che non possano fare clic sul modulo principale e mettere un thread. ?

  • Come risolveresti questo problema? Usereste anche un backgroundworker che esegue un ciclo attraverso l'array di file o esiste un modo più semplice ed efficiente. Forse non usi DownloadFileAsync, ma il download manuale del socket?

  • Quello che voglio fondamentalmente è scaricare i file in modo sincrono, ma avere la mia interfaccia utente (quindi ho bisogno di utilizzare le funzioni di download asincrono). Haha

Spero di averti informato abbastanza. Grazie in anticipo.

È stato utile?

Soluzione

Usa una finestra di dialogo modale, magari aggiungendo una barra di avanzamento per gratificazione visiva, informando l'utente che un processo sta funzionando.

In questo modo puoi eseguire il download in modo asincrono senza consentire l'interazione con i controlli del modulo principale.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top