C#:多线程下载问题

在我对下面这段多线程 下载代码进行测试时,发现下载到的文件都不能正确打开。查看[文件名]-0等文件时,发现文件一开头是正确数据,然而到了一定点后全都是0。请问是什么问题?要怎么解决?源码如下:


public class Download {
    private string URL    {get;set;}  
    private string Path   {get;set;}
    private long   Length {get;set;}
    private int    Threads{get;set;}

    private int  Changeds = 0;
    private bool Stop     = false;

    public Download(string dURL,string dPath = null,int dThreads = 64) {
        URL = dURL;
        Path = dPath;
        Threads = dThreads;
  TestDo();
      }

    private void TestDo() {
        try {
            Path = Path==null ? new Uri(URL).Segments[new Uri(URL).Segments.Length-1] : Path;
            ServicePointManager.DefaultConnectionLimit = Threads + 1;
            ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3 | SecurityProtocolType.SystemDefault | SecurityProtocolType.Tls | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12 | SecurityProtocolType.Tls13;
            Thread[] downThs = new Thread[Threads+1];
            Thread   pushThs = new Thread(() => {
                try {for (;;) {
                    if (Changeds == Threads+1) {
                        var write =  new FileStream(Path,FileMode.Create,FileAccess.Write);
                        for (int i = 0;i < Threads+1;++i) {
                            var read = new FileStream(Path + "-" + i,FileMode.Open,FileAccess.Read);
                            byte[] buffer = new byte[read.Length];
                            read.Read(buffer,0,buffer.Length);
                            write.Write(buffer,0,buffer.Length);
                            read.Close();
                            #if DEBUG
                            #else
                                File.Delete(Path + "-" + i);
                            #endif
                            Console.WriteLine("文件"+i+"完成合并");
                        }
                        write.Close();
                        break;
                    } else {
                        Thread.Sleep(200);
                    }
                }
                } catch(Exception) {
                    throw;
                    //Changeds = -1;
                }
            });
            Length = WebRequest.Create(URL).GetResponse().ContentLength;
            Console.WriteLine("下载开始!文件大小:"+Length);
            for (int i = 0;i<downThs.Length-1;++i) {
                downThs[i] = new Thread( id => {
                    try{long start = ((Length - (Length % Threads)) / Threads) * (int)id;
                    long end   = start + ((Length - (Length % Threads)) / Threads) - 1;
                    Console.WriteLine("下载开始!我的任务是{0}~{1}",start,end);
                    HttpWebRequest thisdown = (HttpWebRequest)WebRequest.Create(URL);
                    thisdown.AddRange(start,end);
                    var readstream = thisdown.GetResponse().GetResponseStream();
                    var writstream = new FileStream(Path + "-" + ((int)id).ToString(),FileMode.Create,FileAccess.Write);
                    byte[] buffer = new byte[end - start];
                    readstream.Read(buffer,0,buffer.Length);
                    writstream.Write(buffer,0,buffer.Length);
                    writstream.Flush();
                    readstream.Close();
                    writstream.Close();
                    Console.WriteLine((int)id + "号线程完成下载任务!");
                    if (Changeds != -1) {
                        ++Changeds;
                    }
                    if (Stop) {
                        File.Delete(Path + "-" + ((int)id).ToString());
                    }}
                    catch(Exception) {
                        Stop = true;
                        File.Delete(Path + "-" + ((int)id).ToString());
                        throw;
                    }
                });
            }
            downThs[downThs.Length-1] = new Thread(id => {
                try{long start = Length - (Length % Threads);
                long end   = Length;
                Console.WriteLine("下载开始!我的任务是{0}~{1}",start,end);
                HttpWebRequest thisdown = (HttpWebRequest)WebRequest.Create(URL);
                thisdown.AddRange(start,end);
                var readstream = thisdown.GetResponse().GetResponseStream();
                var writstream = new FileStream(Path + "-" + ((int)id).ToString(),FileMode.Create,FileAccess.Write);
                byte[] buffer = new byte[end - start];
                readstream.Read(buffer,0,buffer.Length);
                writstream.Write(buffer,0,buffer.Length);
                writstream.Flush();
                readstream.Close();
                writstream.Close();
                Console.WriteLine((int)id + "号线程完成任务!");
                if (Changeds != -1) {
                    ++Changeds;
                }
                if (Stop) {
                    File.Delete(Path + "-" + ((int)id).ToString());
                }}
                catch (Exception) {
                    Changeds = -1;
                    Stop = true;
                    throw;
                }
            });
            pushThs.Start();
            for (int i = 0;i < downThs.Length;++i) {
                downThs[i].Start(i);
            }
            pushThs.Join();
            Console.WriteLine("完成下载!");
        } catch (Exception) {
            throw;
        }
    }
}
byte[] buffer = new byte[end - start];
readstream.Read(buffer,0,buffer.Length) 
 writstream.Write(buffer,0,buffer.Length)

你这代码我看不下去,园子味太重了。我就来说说这3句。别的地方不谈,就这3句就有可能写的都是0

假设 byte[10000] 但read成功了1000,那么剩下9000就都是0对么
所以
stream.Read 是带返回值的,他告诉你他成功读取了多少个

var readcount= readstream.Read(buffer,0,buffer.Length)

你先别合并,看下每个分块下载的对不对,不是所有的服务器都支持请求分块下载的。
如果不对,看你后续分块下载是不是有问题
否则就是合并的问题。

  • 你可以看下这个问题的回答https://ask.csdn.net/questions/7732331
  • 这篇博客也不错, 你可以看下C#实现多个字符串循环输入、有选择对比和输出数字
  • 除此之外, 这篇博客: C#中的三种注释与其快捷键、以及创建类时自动生成类的注释的设置方法中的 按照以上路径查找,最终呈现的窗体为: 部分也许能够解决你的问题, 你可以仔细阅读以下内容或跳转源博客中阅读:
  • 在这里插入图片描述

  • 您还可以看一下 王进老师的跟着王进老师学开发之C#篇第五季:图书管理系统课程中的 图书类别窗体设计小节, 巩固相关知识点
  • 以下回答由chatgpt基于相关博客总结生成:

    优化后的问题内容: 我正在使用下面的代码进行多线程下载的测试。下载完成后,我发现文件无法正确打开。对于下载的文件,例如 [文件名]-0,我注意到文件开头部分包含正确的数据,但是在某个点之后都变成了0。请问这个问题是由什么原因引起的?我应该如何修改源代码以解决这个问题?以下是我的源代码:

    // 请在此处插入您的源代码
    

    首先,对于多线程下载可能导致文件损坏的问题,可能出现以下几个原因:

    1. 多个线程同时写入同一个文件,可能导致数据的覆盖或错位。
    2. 文件写入过程中的并发访问可能导致数据冲突。
    3. IO操作的顺序或时序问题可能导致数据损坏。

    为了解决这个问题,您可以采取以下步骤:

    1. 确保每个线程都在独立的文件中写入数据。可以为每个线程创建一个唯一的文件名(例如,在文件名后面添加线程ID)。
    2. 使用锁机制来保护文件写入,以避免并发写入导致的覆盖或错位。在每个线程写入文件之前,使用一个全局锁来确保每个线程依次进行写入操作。
    3. 确保文件写入的顺序是正确的。您可以使用线程间的同步机制,例如信号量,来控制写入操作的时序。

    以下是一个示例代码,展示了如何根据上述建议来修改多线程下载的源代码:

    using System;
    using System.IO;
    using System.Net;
    using System.Threading;
    
    public class Program
    {
        static object fileLock = new object();
    
        public static void Main()
        {
            string url = "http://example.com/file.ext";
            int numThreads = 4;
            string outputFileName = "output";
    
            // Create threads
            Thread[] threads = new Thread[numThreads];
            for(int i = 0; i < numThreads; i++)
            {
                threads[i] = new Thread(() => DownloadFile(url, outputFileName, i));
                threads[i].Start();
            }
    
            // Wait for all threads to finish
            foreach(Thread thread in threads)
            {
                thread.Join();
            }
    
            Console.WriteLine("Download completed");
        }
    
        public static void DownloadFile(string url, string outputFileName, int threadId)
        {
            try
            {
                // Create unique output filename for each thread
                string threadOutputFileName = outputFileName + "-" + threadId;
    
                // Create a web request to download the file
                HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
                request.AddRange(GetFileRange(threadId, numThreads));
    
                using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
                using (Stream responseStream = response.GetResponseStream())
                using (FileStream fileStream = new FileStream(threadOutputFileName, FileMode.Create))
                {
                    byte[] buffer = new byte[8192];
                    int bytesRead;
                    while ((bytesRead = responseStream.Read(buffer, 0, buffer.Length)) > 0)
                    {
                        // Lock the file before writing
                        lock (fileLock)
                        {
                            fileStream.Write(buffer, 0, bytesRead);
                        }
                    }
                }
            }
            catch(Exception e)
            {
                Console.WriteLine("Thread {0} failed: {1}", threadId, e.Message);
            }
        }
    
        public static long[] GetFileRange(int threadId, int numThreads)
        {
            // Calculate the range each thread should download
            long fileSize = GetRemoteFileSize(url);
            long startOffset = (fileSize / numThreads) * threadId;
            long endOffset = (threadId == numThreads - 1) ? fileSize - 1 : (fileSize / numThreads) * (threadId + 1) - 1;
            return new long[] { startOffset, endOffset };
        }
    
        public static long GetRemoteFileSize(string url)
        {
            using (WebClient client = new WebClient())
            {
                client.OpenRead(url);
                return Int64.Parse(client.ResponseHeaders[HttpResponseHeader.ContentLength]);
            }
        }
    }
    

    请确保根据实际情况修改代码中的变量和URL。这段代码通过使用文件锁和线程间的同步机制来保护文件写入,以确保数据的完整性。每个线程下载的文件将被保存在不同的文件中,避免了数据覆盖和错位。同时,通过限制每个线程的下载范围,确保了文件写入的顺序和时序。

    请注意,以上代码仅为示例,具体的实现方式可能根据你的需求而有所不同。