5

I want to download a file with 6 threads to speed up the process, so this is the part code which calculates the amount of file size for each thread:

string url = "http://somefile.mp3";
List<FileDownloader> filewonloadersList = new List<FileDownloader>();
System.Net.WebRequest req = System.Net.HttpWebRequest.Create(url);
var response = req.GetResponse();
req.Method = "HEAD";
System.Net.WebResponse resp = req.GetResponse();
int responseLength = int.Parse(resp.Headers.Get("Content-Length"));
int parts = 6;
var eachSize = responseLength / parts;
var lastPartSize = eachSize + (responseLength % parts);
for (int i = 0; i < parts - 1; i++)
{
    filewonloadersList.Add(new FileDownloader(url, i * eachSize, eachSize));
}
filewonloadersList.Add(new FileDownloader(url, (parts - 1) * eachSize, lastPartSize));
var threads = new List<Thread>();
foreach (var item in filewonloadersList)
{
    var newThread = new Thread(DoDownload);
    threads.Add(newThread);
    newThread.Start(item);
}

And this is the body of DoDownload function:

public static void DoDownload(object data)
{
    retry:
        try
        {
            var downloader = data as FileDownloader;
            HttpWebRequest req = (HttpWebRequest)WebRequest.Create(downloader.Url);
            if (downloader.Start > 0)
            {
                req.AddRange(downloader.Start, downloader.Start + downloader.Count - 1);
            }
            else
            {
                req.AddRange(downloader.Start, downloader.Start + downloader.Count - 1);
            }
            var response = req.GetResponse();
            using (var reponseStream = response.GetResponseStream())
            {
                using (var fs = new FileStream($"temp_{downloader.Start}.sth", FileMode.OpenOrCreate))
                {
                    var buffer = new byte[1024];
                    int bytesRead = 0;
                    do
                    {
                        bytesRead = reponseStream.Read(buffer, 0, 1024);
                        fs.Write(buffer, 0, bytesRead);
                        fs.Flush();
                    } while (bytesRead > 0);
                    fs.Close();
                }
            }
        }
        catch (WebException e)
        {
            if (e.Status == WebExceptionStatus.Timeout || e.Status == WebExceptionStatus.KeepAliveFailure)
                goto retry;
        }
    }

In addition this is the FileDownloader definition:

public class FileDownloader
{
    public int Start;
    public int Count;
    public string PathTemp;
    public string Url;
    public FileDownloader(string url, int start, int count)
    {
        Url = url;
        Start = start;
        Count = count;
    }
}

Everything is just working as I expected, the length of the file is exactly as much as it should be. Also after I merging the part of downloaded file, it's working properly. The problem is the threading part. I expected 6 files are being downloaded at the same time but they are being downloaded one by one, e.g. when the first part is completed, the second one will be downloaded. How should I fix that?

UPDATE

Based on suggestions, I have changed the function to async:

public async Task DoDownload(object data)
        {
            retry:
            try
            {
                var downloader = data as FileDownloader;
                HttpWebRequest req = (HttpWebRequest)WebRequest.Create(downloader.Url);
                req.AddRange(downloader.Start, downloader.Start + downloader.Count - 1);
                var response = await req.GetResponseAsync();
                using (var reponseStream = response.GetResponseStream())
                {
                    using (var fs = new FileStream($"temp_{downloader.Start}.sth", FileMode.OpenOrCreate))
                    {
                        var buffer = new byte[1024];
                        int bytesRead = 0;
                        do
                        {
                            //reponseStream.Seek(downloader.Start, SeekOrigin.Current);
                            bytesRead = await reponseStream.ReadAsync(buffer, 0, 1024);
                            await fs.WriteAsync(buffer, 0, bytesRead);
                            await fs.FlushAsync();
                        } while (bytesRead > 0);
                        fs.Close();
                    }
                }
            }
            catch (WebException e)
            {
                if (e.Status == WebExceptionStatus.Timeout || e.Status == WebExceptionStatus.KeepAliveFailure)
                    goto retry;
            }
        }

And the foreach loop, in which the parts are populating:

foreach (var item in filewonloadersList)
            {
                Task.WhenAll(DoDownload(item));
            }

But the result is the same! Just one part of a file is downloading at the same time.

Saman Gholami
  • 3,416
  • 7
  • 30
  • 71
  • 3
    You should use async & `await Task.WhenAll()` instead of threads. – SLaks Jun 19 '17 at 17:34
  • Maybe set newThread.IsBackground = true – Rand Random Jun 19 '17 at 17:34
  • 1
    Btw, you know that your code with req.AddRange is the same in the if and else block? – Rand Random Jun 19 '17 at 17:36
  • @RandRandom Yes I know, Honestly at first it was different, but due to some bugs I've changed each section – Saman Gholami Jun 19 '17 at 17:41
  • 1
    Will this really do anything? Even if you're downloading a multipart form, will this speed anything up. Isn't the issue trottled by upload speed, If anything I could see using async, but could someone explain the benefit of using a thread over async? – johnny 5 Jun 19 '17 at 17:49
  • @SLaks Thank you for your suggestion, I have changed it but the result is the same, let me know if I did it wrong please. – Saman Gholami Jun 19 '17 at 18:06
  • Are you sure the parts are downloaded one after the other, or [are there actually two downloads active at a time](https://stackoverflow.com/questions/2960056/trying-to-run-multiple-http-requests-in-parallel-but-being-limited-by-windows)? – CodeCaster Jun 19 '17 at 18:09
  • 2
    Having 6 different threads all trying to handle the response isn't going to make your network connection any better or faster. You aren't creating 6 different physical network connections just by creating a few extra threads. – Servy Jun 19 '17 at 18:09
  • 1
    @Servy yeah I think that's the point that johnny tried to make. However there are of course web applications that throttle their upload per connection, which you might circumvent by opening multiple concurrent connections. Remember download managers? – CodeCaster Jun 19 '17 at 18:12
  • @Servy Sure, I know that, but how a program like "Internet Download Manager" works? – Saman Gholami Jun 19 '17 at 18:12
  • @CodeCaster There's only one file, not multiple files, and it's all with a single connection to a single other location. If there *were* actually multiple connections then that would be different. – Servy Jun 19 '17 at 18:13
  • 1
    @Servy I (and hopefully the OP) am talking about the scenario where both server and client have bandwidth to spare, but where the server limits bandwidth per TCP connection (for whatever reason), which you can circumvent by issuing parallell HTTP requests, each over their own channel. – CodeCaster Jun 19 '17 at 18:16
  • @CodeCaster You were right, the link you've placed in your comment was the key. Thank you for your effort. – Saman Gholami Jun 19 '17 at 18:23

1 Answers1

6

Based on this link Trying to run multiple HTTP requests in parallel, but being limited by Windows (registry). In short, It is matter of ServicePoint. Which provides connection management for HTTP connections. The default maximum number of concurrent connections allowed by a ServicePoint object is 2.

I just needed to add a single nice line to the code:

System.Net.ServicePointManager.DefaultConnectionLimit = 1000;

It's working like a charm! There are multiple files at the same time, and also there is no difference between the way that async and Thread are working. At least both of them are producing my desired result.

Saman Gholami
  • 3,416
  • 7
  • 30
  • 71