Bandysol 2023-07-21 20:49 采纳率: 83.3%
浏览 34

C#:奇怪的操作超时错误

我运行以下代码时,发现在默认超时时间100s后,出现一堆
System.WebException: 操作超时
的错误。加追踪后发现,ID为2、4、6等的线程均出错,而0 1 3 5等则正常完成下载。代码如下(无追踪版):

public class ThreadDownload {
    private volatile Array<bool>        DownloadEndFlag;
    private volatile Array<Array<byte>> DownloadBuffer;
    private          long               DownloadEndBytes = 0;

    public ThreadDownload(string URL,uint Threads = 32,string Path = null) {
        HttpsStarts();
        Path = Path==null ? GetURLFileName(URL) : Path;
        Threads += 1;
        DownloadEndFlag = new ((int)Threads);
        DownloadBuffer  = new ((int)Threads);
        long Length = GetFileLength(URL);
        long ThreadDownloadLong = (Length - Length % (Threads - 1)) / (Threads - 1);
        if (CanAddRange(URL)) {
            Thread PushThread = new (info => {
                PushInfo Info = (PushInfo)info;
                if (File.Exists(Info.Path)) {
                    File.Delete(Info.Path);
                }
                uint DownloadEndKey = 0;
                for (;DownloadEndKey < Info.Threads;) {
                    if (DownloadEndFlag[(int)DownloadEndKey]) {
                        using (var write = new FileStream(Path,FileMode.Append,FileAccess.Write)) {
                            write.Write(DownloadBuffer[(int)DownloadEndKey],0,DownloadBuffer[(int)DownloadEndKey].Length);
                        }
                        ++DownloadEndKey;
                    } else {
                        Thread.Sleep(150);
                    }
                }
            });
            Array<Thread> DownThread = new Thread[Threads];
            PushThread.Start(new PushInfo(Length,Threads,Path));
            for (int i = 0;i < DownThread.Length;++i) {
                DownloadInfo t = new DownloadInfo(
                    ThreadDownloadLong*i,
                    i==DownThread.Length-1 ? Length : ThreadDownloadLong*(i+1),
                    (uint)i,
                    URL);
                DownThread[i] = new Thread(info => {
                    DownloadInfo Info = (DownloadInfo)info;
                    DownloadBuffer[(int)Info.ID] = new ((int)(Info.End-Info.Start+1));
                    var t = (HttpWebRequest)HttpWebRequest.Create(Info.URL);
                    t.AddRange(Info.Start,Info.End);
                    var t3 = t.GetResponse();//报错位置
                    var t2 = t3.GetResponseStream();
                    ReadAllData(t2,DownloadBuffer[(int)Info.ID],(uint)(Info.End-Info.Start+1));
                    t3.Close();
                    DownloadEndFlag[(int)Info.ID] = true;
                });
                DownThread[i].Start(t);
            }
            PushThread.Join();
        } else {
            new Download(URL,Path);
        }
    }

    private struct DownloadInfo {
        public long Start;
        public long End;
        public uint ID;
        public string URL;

        public DownloadInfo(long s,long e,uint i,string u) {
            Start = s;
            End = e;
            ID = i;
            URL = u;
        }
    }

    private struct PushInfo {
        public long Length;
        public uint Threads;
        public string Path;

        public PushInfo(long l,uint t,string p) {
            Length = l;
            Threads = t;
            Path = p;
        }
    }
}

internal static class DownloadHelperClass {
    public static bool CanAddRange(string URL) {
        var readreturntmp1 = ((HttpWebRequest)WebRequest.Create(URL));
        readreturntmp1.AddRange(0,1);
        var ret = ((HttpWebResponse)readreturntmp1.GetResponse()).Headers;
        for (int i = 0;i < ret.AllKeys.Length;++i) {
            if (ret.AllKeys[i].Equals("Accept-Ranges") && ret[i].Equals("bytes")) {
                return true;
            } else if (ret.AllKeys[i].Equals("Accept-Ranges") && ret[i].Equals("none")) {
                return false;
            }
        }
        return false;
    }
    public static void HttpsStarts() {
        ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3 | SecurityProtocolType.SystemDefault | SecurityProtocolType.Tls | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12 | SecurityProtocolType.Tls13;
    }
    public static string GetURLFileName(string URL) {
        var t = new Uri(URL).Segments;
        return t[t.Length-1];
    }
    public static void ReadAllData(Stream read,byte[] array,uint DataLong) {
        array = new byte[DataLong];
        for (uint key = 0;key < DataLong;) {
            key += (uint)read.Read(array,(int)key,(int)(DataLong-key));
        }
    }
    public static long GetFileLength(string URL) {
        long length = 0;
        var req = (HttpWebRequest)WebRequest.Create(URL);
        req.Method = "HEAD";
        var res = (HttpWebResponse)req.GetResponse();
        if (res.StatusCode == HttpStatusCode.OK) {
            length = res.ContentLength;  
        }
        res.Close();
        return length;
    }
}
  • 写回答

1条回答 默认 最新

  • wanghui0380 2023-07-22 08:24
    关注

    还在弄这个。其实问题早清楚了。
    只不过我们没明说(因为我们知道不能跟喜欢园子风格的人谈这些,他们不会听的)
    不过你弄了这么长时间,我们就不得不说了

    1. 网络下载属于IO,其实跟你下多少线程没关系。假设5个线程就已经把下行带宽占满,那么你就是开32线程,64线程也没啥用。
      相反你开的线程越多,超时就越多。因为他们是竞争关系,你抢了20M带宽,我就少了20M带宽。所以就有线程完全抢不到带宽然后超时
      so:打开迅雷,打开快车这类工具,你会看到这类多线程下载工具,大部分情况下都是限制一个文件5线程,同时最多5个文件。
      而且现象也是明显了,单纯开一个文件5线程,假设下载是5M/s,你在开另一个文件同时下载,速度可能立马下降到1M(因为有另外的5个线程去抢网络IO了)
      所以上个帖子我们用SemaphoreSlim信号量控制 并行线程数量
      别认为线程32,64就比5快!!常见场景挤公交,是32,64个人堵门挤,还是弄个5个闸道排队进快?SemaphoreSlim就是闸道这种东西,SemaphoreSlim(5)个闸道同时使用,每个闸道出一个进一个。毕竟网络IO本身是固定资源,不是人多了门就多了这种资源

    2.有关分块,你是分线程,使用32线程,64线程控制。我给你的代码是分块,每块4M。因为我除了需要控制同时使用闸道的人以外,我还需要控制他们的使用时间,毕竟前面策略是出一个进一个,那么进去的这个不能太占时间,还是抢公交,假设进去的5个人吵起来了,他们不出去,后面的就得等了。所以我们限制每块4M---别问我们4M怎么来的,这个是经验参数。绝大部分网络参数设置默认就是4M,这是前面的前辈们调优的经验参数。如果换成固定32线程,大小就不固定了。也许你一个线程就128M,那么他占用的时间就比4M要长的多了

    3.线程执行没有顺序,你可以多运行几次。他是系统调度,所以ID为2、4、6等的线程均出错是随机的,而非固定的。

    评论 编辑记录

报告相同问题?

问题事件

  • 创建了问题 7月21日

悬赏问题

  • ¥15 x趋于0时tanx-sinx极限可以拆开算吗
  • ¥500 把面具戴到人脸上,请大家贡献智慧
  • ¥15 任意一个散点图自己下载其js脚本文件并做成独立的案例页面,不要作在线的,要离线状态。
  • ¥15 各位 帮我看看如何写代码,打出来的图形要和如下图呈现的一样,急
  • ¥30 c#打开word开启修订并实时显示批注
  • ¥15 如何解决ldsc的这条报错/index error
  • ¥15 VS2022+WDK驱动开发环境
  • ¥30 关于#java#的问题,请各位专家解答!
  • ¥30 vue+element根据数据循环生成多个table,如何实现最后一列 平均分合并
  • ¥20 pcf8563时钟芯片不启振