4 votes

Téléchargement parallèle

J'essaie de réaliser un téléchargement parallèle de fichiers via http en C#. J'ai essayé plusieurs approches différentes, mais aucune ne semble fonctionner correctement. Peu importe ce que je fais, les téléchargements finissent par être mis en file d'attente et ne fonctionnent pas de manière réellement parallèle.

Quelqu'un pourrait-il me donner des indications ou m'indiquer un lien vers un article décrivant une méthode qui fonctionne réellement ?

5voto

alexl Points 4535

J'ai juste écrit un peu de code, je ne l'ai pas testé, j'attends des observations, merci à tous :

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);
            }
        }
    }

Vous pouvez le tester avec ceci :

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" });

0voto

Nick Randell Points 4133

Est-ce parce que vous utilisez une machine à un seul cœur ?

Le TPL utilisera autant de threads que vous avez de cœurs. Il existe des moyens de le faire fonctionner avec plus de threads si vous le souhaitez.

0voto

Dogu Arslan Points 1571

Le téléchargement d'un fichier est un appel lié à l'E/S, donc parallèle ou non, la première chose dont vous devez vous assurer est que vous faites un appel asynchrone sans thread pour télécharger un seul fichier. Les méthodes telles que Task.Run, task.Start sont basées sur des threads et ne doivent pas être utilisées pour des appels liés aux E/S, sinon vous lancerez les téléchargements en parallèle mais vous bloquerez immédiatement l'ensemble de votre CPU, chaque cœur restant inactif en attendant le retour de l'appel de téléchargement.

Vous devriez plutôt utiliser le modèle async/await et attendre votre méthode de téléchargement asynchrone. Cette méthode est sans fil, à condition que vous disposiez d'une véritable méthode de téléchargement asynchrone, mais la plupart des bibliothèques la fournissent.

Maintenant, si vous parallélisez cet appel E/S, stockez toutes les tâches retournées dans une collection et à la fin, vous pouvez utiliser await Tasks.WhenAll(tasks) ; pour attendre toutes les tâches.

Vous devez également veiller à ce que les appels asynchrones liés aux E/S ne privent pas le pool de connexion d'E/S d'une alimentation suffisante. Vous pouvez donc limiter le nombre d'appels E/S simultanés.

J'ai implémenté une API de traitement parallèle qui vous permet de faire des appels asynchrones simultanés sans thread avec des options de limitation des E/S, etc.

N'hésitez pas à y jeter un coup d'œil et à l'utiliser : https://www.nuget.org/packages/ParallelProcessor/

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X