Pergunta

Se você não se incomodar em ler todo o texto, pode pular para os dois últimos pontos: p

Este site já me ajudou uma dúzia de vezes no passado, mas agora eu realmente preciso de ajuda.

O problema é o seguinte:

  • primeiro, usei a função DownloadFile com a opção show UI. Funcionou muito bem, mas a IU é feia e não há muitas opções disponíveis.

  • Mudei então para DownloadFileAsync com o evento de progresso alterado para ter basicamente minha própria IU. O único problema que tive é que percorro uma lista de arquivos que o programa precisa baixar e chamo a função de download (que chama a função DownloadAsync). Assim:

    foreach (ListViewItem t in themeList.CheckedItems)
            {
                DownloadFile(file to be downloaded);
            }
    
  • Mas obviamente isso não funcionou, já que a função DownloadFileAsync não oferece suporte a várias chamadas ao mesmo tempo porque não há um sistema de fila como o DownloadFile, então ele só baixará o primeiro arquivo chamado. Então, o que fiz foi criar uma função que adiciona o arquivo a ser baixado a um array e faz um backgroundworker percorrer a lista e esperar com a chamada de DownloadAsync até que o download anterior seja concluído. Isso meio que funcionou. Aqui está o 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
    
  • Então, basicamente, meu próximo problema é que o programa tem que esperar para executar mais códigos até que os downloads sejam concluídos. Eu fiz isso fazendo:

    while (_fd.Count != 0)
            Application.DoEvents();
    
  • Obviamente, essa não é a melhor solução, pois eles podem clicar em outras coisas enquanto os downloads estão ocupados, mas sim, Thread.Sleep apenas congelaria tudo. Em vez disso, faria um formulário de espera (talvez aqui uma barra de progresso em vez de no formulário principal) com foco nele no topo do formulário principal, para que eles não pudessem clicar no formulário principal e colocar um Thread.Sleep no formulário principal ?

  • Como você resolveria isso? Você também usaria um backgroundworker que faz um loop através do array de arquivos ou existe uma maneira mais fácil e eficiente. Talvez não usando DownloadFileAsync, mas fazendo download manual de soquete?

  • O que eu basicamente desejo é baixar arquivos de forma síncrona, mas tenho minha própria IU (então preciso usar funções de download assíncronas). Haha

Espero ter informado você o suficiente. Agradecemos antecipadamente.

Foi útil?

Solução

Use uma caixa de diálogo modal, talvez adicionando uma barra de progresso para gratificação visual, informando ao usuário que um processo está funcionando.

Dessa forma, você pode realizar o download de forma assíncrona, sem permitir a interação com os controles do formulário principal.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top