并行下载

本文关键字:下载 并行 | 更新日期: 2023-09-27 18:00:57

我正在尝试通过C#中的http实现文件的并行下载。我尝试了几种不同的方法,但似乎都不正确。无论我做什么,下载都会被排队,并且不能以真正的并行方式工作。

有人能给我一些指导或链接到一篇描述实际有效方法的文章吗?

并行下载

我只是写了一些代码,没有测试它,等待一些观察谢谢大家:

public class DownloadFile
{
    public string Url { get; set; }
    public string PathToSave { get; set; }
}

public class ParallelDownloading
    {
        private ConcurrentQueue<DownloadFile> _queueToDownlaod;
        private IList<Task> _downloadingTasks;
        private Timer _downloadTimer;
        private int _parallelDownloads;
        public ParallelDownloading(int parallelDownloads)
        {
            _queueToDownlaod = new ConcurrentQueue<DownloadFile>();
            _downloadingTasks = new List<Task>();
            _downloadTimer = new Timer();
            _parallelDownloads = parallelDownloads;
            _downloadTimer.Elapsed += new ElapsedEventHandler(DownloadTimer_Elapsed);
            _downloadTimer.Interval = 1000;
            _downloadTimer.Start();
            ServicePointManager.DefaultConnectionLimit = parallelDownloads;
        }
        public void EnqueueFileToDownload(DownloadFile file)
        {
            _queueToDownlaod.Enqueue(file);
        }
        void DownloadTimer_Elapsed(object sender, ElapsedEventArgs e)
        {
            StartDownload();
        }
        private void StartDownload()
        {
            lock (_downloadingTasks)
            {
                if (_downloadingTasks.Count < _parallelDownloads && _queueToDownlaod.Count > 0)
                {
                    DownloadFile fileToDownload;
                    if (_queueToDownlaod.TryDequeue(out fileToDownload))
                    {
                        var task = new Task(() =>
                        {
                            var client = new WebClient();
                            client.DownloadFile(fileToDownload.Url, fileToDownload.PathToSave);
                        }, TaskCreationOptions.LongRunning);
                        task.ContinueWith(DownloadOverCallback, TaskContinuationOptions.None);
                        _downloadingTasks.Add(task);
                        task.Start();
                    }      
                }
            }
        }
        public void DownloadOverCallback(Task downloadingTask)
        {
            lock (_downloadingTasks)
            {
                _downloadingTasks.Remove(downloadingTask);
            }
        }
    }

你可以用这个测试它:

ParallelDownloading p = new ParallelDownloading(5);
        p.EnqueueFileToDownload(new DownloadFile() { PathToSave = @"c:'file1.f", Url = @"http://download.thinkbroadband.com/20MB.zip" });
        p.EnqueueFileToDownload(new DownloadFile() { PathToSave = @"c:'file2.f", Url = @"http://download.thinkbroadband.com/20MB.zip" });
        p.EnqueueFileToDownload(new DownloadFile() { PathToSave = @"c:'file3.f", Url = @"http://download.thinkbroadband.com/20MB.zip" });
        p.EnqueueFileToDownload(new DownloadFile() { PathToSave = @"c:'file4.f", Url = @"http://download.thinkbroadband.com/20MB.zip" });
        p.EnqueueFileToDownload(new DownloadFile() { PathToSave = @"c:'file5.f", Url = @"http://download.thinkbroadband.com/20MB.zip" });
        p.EnqueueFileToDownload(new DownloadFile() { PathToSave = @"c:'file6.f", Url = @"http://download.thinkbroadband.com/20MB.zip" });
        p.EnqueueFileToDownload(new DownloadFile() { PathToSave = @"c:'file7.f", Url = @"http://download.thinkbroadband.com/20MB.zip" });
        p.EnqueueFileToDownload(new DownloadFile() { PathToSave = @"c:'file8.f", Url = @"http://download.thinkbroadband.com/20MB.zip" });
        p.EnqueueFileToDownload(new DownloadFile() { PathToSave = @"c:'file9.f", Url = @"http://download.thinkbroadband.com/20MB.zip" });
        p.EnqueueFileToDownload(new DownloadFile() { PathToSave = @"c:'file10.f", Url = @"http://download.thinkbroadband.com/20MB.zip" });
        p.EnqueueFileToDownload(new DownloadFile() { PathToSave = @"c:'file11.f", Url = @"http://download.thinkbroadband.com/20MB.zip" });
        p.EnqueueFileToDownload(new DownloadFile() { PathToSave = @"c:'file12.f", Url = @"http://download.thinkbroadband.com/20MB.zip" });
        p.EnqueueFileToDownload(new DownloadFile() { PathToSave = @"c:'file13.f", Url = @"http://download.thinkbroadband.com/20MB.zip" });
        p.EnqueueFileToDownload(new DownloadFile() { PathToSave = @"c:'file14.f", Url = @"http://download.thinkbroadband.com/20MB.zip" });
        p.EnqueueFileToDownload(new DownloadFile() { PathToSave = @"c:'file15.f", Url = @"http://download.thinkbroadband.com/20MB.zip" });
        p.EnqueueFileToDownload(new DownloadFile() { PathToSave = @"c:'file16.f", Url = @"http://download.thinkbroadband.com/20MB.zip" });
        p.EnqueueFileToDownload(new DownloadFile() { PathToSave = @"c:'file17.f", Url = @"http://download.thinkbroadband.com/20MB.zip" });
        p.EnqueueFileToDownload(new DownloadFile() { PathToSave = @"c:'file18.f", Url = @"http://download.thinkbroadband.com/20MB.zip" });
        p.EnqueueFileToDownload(new DownloadFile() { PathToSave = @"c:'file19.f", Url = @"http://download.thinkbroadband.com/20MB.zip" });

这是因为您在单核机器上运行吗?

TPL将使用尽可能多的线程,因为你有核心。如果你想的话,有一些方法可以让它使用更多的线程运行。

下载文件是一个I/O绑定调用,因此无论是否并行,您首先应该确保的是您正在进行一个无线程异步调用来下载单个文件。像Task、Run、Task这样的方法。Start是基于线程的,不应用于I/O绑定调用,否则您将并行启动的下载,但您将立即阻塞整个CPU,每个核心都闲置在那里等待下载调用返回。

相反,您应该使用async/await模式并等待您的异步下载方法。这是无线程的,假设您有一个真正的异步下载方法,但大多数库都提供了这种方法。

现在,如果你并行化这个I/O调用,将所有返回的任务存储在一个集合中,最后你可以使用wait tasks.WhenAll(tasks(;等待所有任务。

在进行并发I/O绑定异步调用时,您还需要确保的一件事是不要耗尽I/O连接池,因此您可能需要限制并发I/O调用的数量。

我已经实现了一个并行处理API,它允许您使用I/O节流选项等进行并发无线程异步调用

请随意查看和使用:https://www.nuget.org/packages/ParallelProcessor/