【问题标题】:C# download asynchronously with queue listC# 使用队列列表异步下载
【发布时间】:2011-11-14 09:23:38
【问题描述】:

如果你懒得看全文,你可以跳到最后两个点:p

这个网站过去已经帮助了我十几次,但现在我自己真的需要帮助。

问题如下:

  • 首先我使用了带有显示 UI 选项的 DownloadFile 函数。这很好用,但 UI 很丑,可用的选项不多。

  • 然后我切换到带有更改的进度事件的 DownloadFileAsync,以便基本上拥有自己的 UI。我遇到的唯一问题是我遍历程序必须下载的文件列表并调用下载函数(调用 DownloadAsync 函数)。像这样:

    foreach (ListViewItem t in themeList.CheckedItems)
            {
                DownloadFile(file to be downloaded);
            }
    
  • 但显然这不起作用,因为DownloadFileAsync函数不支持同时调用多个调用,因为没有像DownloadFile那样的队列系统,所以它只会下载第一个调用的文件。 所以我所做的是创建一个函数,将要下载的文件添加到一个数组中,并在列表中进行一个后台工作循环,并等待调用 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,而是手动下载socket?

  • 我基本上想要的是同步下载文件,但有自己的UI(所以我需要使用异步下载功能)。哈哈

我希望我已经通知了你足够多的信息。提前致谢。

【问题讨论】:

  • 也许这是一篇“TL;DR”帖子?
  • 我会让它更简短,因为它确实包含一些不必要的东西
  • 您关于第 5 条的计划听起来是最好的方法。您也可以尝试使用任务并行库 (TPL)。它将简化并发下载调用,并且您可以使用同步文件下载器。

标签: c# downloadfile downloadfileasync


【解决方案1】:

使用模态对话框,可能会添加一个进度条以获得视觉上的满足感,通知用户进程正在运行。

这样您就可以执行异步下载,而无需与主窗体的控件进行交互。

【讨论】:

  • 谢谢,我会查看模态对话框。
  • 我似乎无法关闭模式对话框以执行更多代码。我尝试在等待表单上设置一个计时器,当进度条达到 100 时,它将 DialogResult 设置为 OK,但它没有关闭。在主窗体的后台工作人员中尝试过,但没有关闭。嗯..
  • 开始下载时,显示模态对话框。在您的异步回调中,您应该处理对话框。
  • 谢谢。在我调用了 DownloadFileAsync 之后,我会显示对话框。它会很好地下载第一个文件并关闭。然后它第二次打开对话框(第二个文件),但它只是坐在那里(空闲和进度条值为 0)。我执行 _wait = new Wait,然后在 DownloadFileAsync 之后立即调用 ShowDialog(this)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-01-21
  • 2019-02-07
  • 2012-12-31
  • 2022-10-19
  • 2020-02-18
  • 1970-01-01
相关资源
最近更新 更多