Pregunta

Si no se molesta en leer todo el texto, puede pasar a los dos últimos puntos: p

Este sitio me ha ayudado una docena de veces en el pasado, pero ahora realmente necesito ayuda.

El problema es el siguiente:

  • Primero utilicé la función DownloadFile con la opción show UI. Esto funcionó muy bien, pero la interfaz de usuario es fea y no hay muchas opciones disponibles.

  • Luego cambié a DownloadFileAsync con el evento de progreso modificado para básicamente tener mi propia interfaz de usuario. El único problema que he tenido es que recorro una lista de archivos que el programa tiene que descargar y llamo a la función de descarga (que llama a la función DownloadAsync). Así:

    foreach (ListViewItem t in themeList.CheckedItems)
            {
                DownloadFile(file to be downloaded);
            }
    
  • Pero obviamente esto no funcionó, ya que la función DownloadFileAsync no admite múltiples llamadas al mismo tiempo porque no hay un sistema de cola como DownloadFile, por lo que solo descargará el primer archivo llamado. Entonces, lo que hice fue crear una función que agregue el archivo que se descargará a una matriz y hacer que un trabajador en segundo plano recorra la lista y espere llamando a DownloadAsync hasta que se complete la descarga anterior. Esto funcionó un poco. Aquí está el código:

    #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
    
  • Básicamente, mi siguiente problema es que el programa tiene que esperar para ejecutar más código hasta que se realizan las descargas. Hice esto haciendo:

    while (_fd.Count != 0)
            Application.DoEvents();
    
  • Obviamente, esta no es la mejor solución, ya que pueden hacer clic en otras cosas mientras las descargas están ocupadas, pero sí, Thread.Sleep simplemente congelaría todo. En su lugar, haría un formulario de espera (tal vez aquí una barra de progreso en lugar de en el formulario principal) con el foco en la parte superior del formulario principal, para que no puedan hacer clic en el formulario principal y poner un Thread.Sleep en el formulario principal ?

  • ¿Cómo solucionaría esto? ¿También usaría un backgroundworker que recorra la matriz de archivos o hay una forma más fácil y eficiente? ¿Quizás no usando DownloadFileAsync, pero descargando sockets manualmente?

  • Lo que quiero básicamente es descargar archivos de forma sincrónica, pero tener mi propia interfaz de usuario (por lo que necesito utilizar funciones de descarga asincrónica). Jaja

Espero haberte informado lo suficiente. Gracias de antemano.

¿Fue útil?

Solución

Utilice un diálogo modal, tal vez agregando una barra de progreso para la gratificación visual, informando al usuario que un proceso está funcionando.

De esta manera, puede realizar su descarga asincrónica sin permitir la interacción con los controles del formulario principal.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top