Параллельная загрузка - PullRequest
4 голосов
/ 01 июля 2011

Я пытаюсь добиться параллельной загрузки файлов через http в C #.Я попробовал несколько разных подходов, но ни один из них, кажется, не работает правильно.Независимо от того, что я делаю, загрузки в конечном итоге ставятся в очередь и не работают по-настоящему параллельно.

Может кто-нибудь дать мне несколько указаний или ссылку на статью, в которой описан метод, который действительно работает?

Ответы [ 3 ]

5 голосов
/ 01 июля 2011

Я просто написал некоторый код, не тестировал его, ожидая некоторого наблюдения, спасибо всем:

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" });
0 голосов
/ 02 февраля 2016

Загрузка файла - это связанный с вводом / выводом вызов, параллельный или нет. Первое, что вы должны убедиться, это то, что вы делаете асинхронный вызов без потока для загрузки одного файла. Такие методы, как Task.Run, task.Start, основаны на потоках, и их не следует использовать для вызовов, связанных с вводом / выводом, в противном случае вы начнете загружать файлы параллельно, но вы сразу же заблокируете весь свой ЦП, каждое ядро ​​в режиме ожидания ожидает вызов загрузки, чтобы вернуться.

Вместо этого вы должны использовать шаблон async / await и ждать вашего метода асинхронной загрузки. Это означает, что у вас есть настоящий асинхронный метод загрузки, но большинство библиотек это предоставляют.

Теперь, если вы распараллелите этот вызов ввода-вывода, сохраните все возвращенные задачи в коллекции, и в конце вы сможете использовать await Tasks.WhenAll (tasks); ждать всех задач.

Одной вещью, которую вам также необходимо убедиться при выполнении одновременных асинхронных вызовов, связанных с вводом / выводом, является не истощение пула соединений ввода / вывода, поэтому вы можете захотеть ограничить количество одновременных вызовов ввода / вывода, которые вы делаете.

Я реализовал API параллельной обработки, который позволяет вам выполнять параллельные асинхронные вызовы без потоков с опциями регулирования ввода-вывода и т. Д.

Не стесняйтесь взглянуть и использовать: https://www.nuget.org/packages/ParallelProcessor/

0 голосов
/ 01 июля 2011

Это потому, что вы работаете на одноядерном компьютере?

TPL будет использовать столько потоков, сколько у вас есть ядер. Есть способы заставить его работать, используя больше потоков, если хотите.

...