Skip to main content
 首页 » 编程设计

c# 为什么WebClient通过线程调用时大部分时间都超时

2024年02月01日27飞鱼

我正在从事一个使用定时 Web 客户端的项目。类结构是这样的。

Controller => 类(class)的主要主管 Form1、SourceReader、ReportWriter、UrlFileReader、HTTPWorker、TimedWebClient。

HTTPworker是给定url时获取页面源的类。 TimedWebClient 是处理WebClient 超时的类。这是代码。

class TimedWebClient : WebClient 
{ 
    int Timeout;  
 
    public TimedWebClient() 
    { 
        this.Timeout = 5000; 
    } 
 
 
      protected override WebRequest GetWebRequest(Uri address) 
    { 
        var objWebRequest = base.GetWebRequest(address); 
        objWebRequest.Timeout = this.Timeout; 
        return objWebRequest; 
    } 
} 

在 HTTPWorker 中我有

 TimedWebClient wclient = new TimedWebClient(); 
 wclient.Proxy = WebRequest.GetSystemWebProxy(); 
 wclient.Headers["Accept"] = "application/x-ms-application, image/jpeg, application/xaml+xml, image/gif, image/pjpeg, application/x-ms-xbap, application/x-shockwave-flash, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*"; 
 wclient.Headers["User-Agent"] = "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; MDDC)"; 
 string pagesource = wclient.DownloadData(requestUrl); 
 UTF8Encoding objUTF8 = new UTF8Encoding(); 
 responseData = objUTF8.GetString(pagesource); 

我在那里处理过异常。 在 Form1 中,我有一个后台 Controller 和一个 urllist。

第一次实现:

首先,我一次获取一个 url,并将其交给 ONLY Controller 对象进行处理。 然后它工作正常。但由于它是连续的,所以当列表太大时会花费很长时间。

第二次实现:

然后在backgroundworker的Do_Work中我做了七个controller和七个thread。每个 Controller 都有唯一的 HTTPWorker 对象。但现在它会抛出异常,提示“超时”。

下面是Form1.cs backgroundworker1_DoWork中的代码。

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e) 
{ 
    bool done = false; 
 
    while (!backgroundWorker1.CancellationPending && !done) 
    { 
        int iterator = 1; 
        int tempiterator = iterator; 
        Controller[] cntrlrarray = new Controller[numofcontrollers]; 
        Thread[] threadarray = new Thread[numofcontrollers]; 
        int cntrlcntr = 0; 
        for ( cntrlcntr = 0; cntrlcntr < numofcontrollers; cntrlcntr++) 
        { 
            cntrlrarray[cntrlcntr] = new Controller(); 
 
        } 
        cntrlcntr = 0;  
        for (iterator = 1; iterator <= this.urlList.Count; iterator++) 
        { 
            int assignedthreads = 0; 
 
            for (int threadcounter = 0; threadcounter < numofcontrollers; threadcounter++) 
            { 
                cntrlcntr = threadcounter; 
                threadarray[threadcounter] = new Thread(() => cntrlrarray[cntrlcntr].Process(iterator - 1)); 
                threadarray[threadcounter].Name = this.urlList[iterator - 1]; 
                threadarray[threadcounter].Start(); 
                backgroundWorker1.ReportProgress(iterator); 
                assignedthreads++; 
 
                if (iterator == this.urlList.Count) 
                { 
                    break; 
                } 
                else 
                { 
                    iterator++; 
                } 
 
            } 
 
            for (int threadcounter = 0; threadcounter < assignedthreads; threadcounter++) 
            { 
                cntrlcntr = threadcounter; 
                threadarray[threadcounter].Join(); 
 
            } 
            if (iterator == this.urlList.Count) 
            { 
                break; 
            } 
            else 
            { 
                iterator--; 
            } 
 
        } 
        done = true; 
    } 
} 

请问这是什么原因及解决方法? Appolgises 太长了。提前谢谢你。

请您参考如下方法:

天空……充满了丝线!不过说真的 - 不要使用这么多线程。这就是异步 I/O 的用途。如果您使用的是 .NET 4.5,则使用 await/async 很容易做到这一点,否则它就是一些样板代码,但它仍然比这个好得多。

除此之外,默认情况下 TCP 连接的数量非常有限。即使一次有 1000 个下载的用途(而且它可能不是,因为您正在共享带宽),您根本无法随意创建和删除 TCP 连接 - 打开 TCP 连接是有限制的( 5 到 20 之间的任何地方,除非你在服务器上)。您可以更改它,但通常更喜欢以不同的方式做事。看这个entry .如果此应用程序不是单独运行(它可能不是,假设您在服务器 Windows 上不会遇到这样的问题),这也可能是一个问题。例如,torrent 客户端经常会遇到半开连接限制(一个仍在等待初始 TCP handkahe 结束的连接)。当然,这对您的应用程序是有害的。

现在,即使您保持在这个限制之下,通信时也可以使用固定数量的出站和入站端口。当您快速打开和关闭 TCP 连接时,这是一个问题,因为 TCP 在后台保持连接大约 4 分钟(以确保没有错误的数据包到达端口,同时可以重用)。这意味着如果您在此时间间隔内创建了足够多的连接,您将“饿死”您的端口池,并且每个 新的 TCP 连接都将被拒绝(因此您的浏览器将暂时停止工作,等等) .).

接下来,5 秒的超时时间非常短。真的。想象一下,完成一次握手需要一秒钟(这是 ~300 毫秒的 ping,这仍然在合理的互联网响应范围内)。突然间,你有了一个新的连接,它必须等待其他握手完成,而这可能需要几秒钟。而这仍然只是连接的开始。然后是 DNS 查找,以及 HTTP 服务器本身的响应……5 秒是一个低超时。

简而言之,这不是多线程 - 它是您打开的大量(无用)连接。此外,对于单个 Web 上的 URL,您应该查看 Keep-Alive 连接 - 它们可以重用已经打开的 TCP 连接,这可以显着缓解此问题。

现在,深入了解这一点。您正在不必要地启动和销毁线程。相反,最好有一个 URL 队列和多个线程使用者,它们将从队列中获取输入。这样,只要队列中有内容,您就只有 7 个(或任何数量)线程从队列中轮询,这可以节省大量系统资源(并提高性能)。我在想你正在做的 Thread.Join 也可能与你的问题有关。即使您是在后台工作人员中运行该程序,也有可能其中发生了一些奇怪的事情。