在c#中multithreading处理大量Web请求

我有一个程序,我需要在外部sharepoint站点创建一些大量的文件夹(外部意思是我不能使用sharepoint对象模型)。 Web请求适用于此,但只需一次执行一个(发送请求,等待响应,重复)相当慢。 我决定multithreading化请求,试图加快速度。 该程序已大大加快,但经过一段时间(1-2分钟左右)后,并发exception开始被抛出。

代码如下,这是最好的方法吗?

Semaphore Lock = new Semaphore(10, 10); List folderPathList = new List(); //folderPathList populated foreach (string folderPath in folderPathList) { Lock.WaitOne(); new Thread(delegate() { WebRequest request = WebRequest.Create(folderPath); request.Credentials = DefaultCredentials; request.Method = "MKCOL"; WebResponse response = request.GetResponse(); response.Close(); Lock.Release(); }).Start(); } for(int i = 1;i <= 10;i++) { Lock.WaitOne(); } 

例外情况是这样的

未处理的exception:System.Net.WebException:无法连接到远程服务器—> System.Net.Sockets.SocketException:通常只允许每个套接字地址使用一次192.0.0.1:81
在System.Net.Sockets.Socket.DoConnect(EndPoint endPointSnapshot,SocketAddre ss socketAddress)
在System.Net.Sockets.Socket.InternalConnect(EndPoint remoteEP)
at System.Net.ServicePoint.ConnectSocketInternal(Boolean connectFailure,Socket s4,Socket s6,Socket&socket,IPAddress&address,ConnectSocketState state,IAsyncResult asyncResult,Int32 timeout,Exception&exception)

您可能会创建太多连接,从而耗尽您可以使用的所有本地端口。 关闭端口后可以重新使用端口的超时时间。 WebRequest为您隐藏所有低级套接字处理,但我猜它最终用完了端口,或者尝试(重新)绑定到已经处于TIME_WAIT状态的套接字。

即使您不关心响应 ,也应确保阅读响应流 。 这应该有助于不产生太多延迟的连接。

 WebResponse response = request.GetResponse(); new StreamReader(response.GetResponseStream()).ReadToEnd(); 

我将从这里粘贴一些相关信息:

当连接关闭时,在关闭连接的一侧,5元组{协议,本地IP,本地端口,远程IP,远程端口}默认进入TIME_WAIT状态240秒。 在这种情况下,协议是固定的 – TCP本地IP,远程IP和远程PORT通常也是固定的。 所以变量是本地端口。 发生的情况是,当您不绑定时,使用1024-5000范围内的端口。 所以粗略地说你有4000个端口。 如果你在4分钟内全部使用它们 – 意味着大约每4秒钟进行16次网络服务呼叫,你将耗尽所有端口。 这就是这个例外的原因。

好的,现在怎么解决?

  1. 其中一种方法是增加动态端口范围。 默认情况下,最大值为5000.您可以将其设置为65534. HKLM \ System \ CurrentControlSet \ Services \ Tcpip \ Parameters \ MaxUserPort是要使用的密钥。

  2. 你可以做的第二件事是一旦连接进入TIME_WAIT状态,你可以减少它处于该状态的时间,默认是4分钟,但你可以将此设置为30秒HKLM \ System \ CurrentControlSet \ Services \ Tcpip \参数\ TCPTimedWaitDelay是要使用的密钥。 将此设置为30秒

您没有关闭可能导致连接长时间打开的webrequest。 对于Parallel.Net的Parallel.Foreach来说,这听起来是一个完美的工作,只需要确定你希望它运行多少个线程

  ParallelOptions parallelOptions = new ParallelOptions(); parallelOptions.MaxDegreeOfParallelism = 10; Parallel.ForEach(folderPathList, parallelOptions, folderPathList => { using(WebRequest request = WebRequest.Create(folderPath)) { request.Credentials = DefaultCredentials; request.Method = "MKCOL"; GetResponse request = WebRequest.Create(folderPath); request.Credentials = DefaultCredentials; request.Method = "MKCOL"; using (WebResponse response = request.GetResponse()); } }); 

要记住的另一件事是maxConnections,请务必在app.config中设置它:

        

在实际场景中,您必须添加try-catch并重试可能超时的连接,从而导致更复杂的代码

对于这种IO密集型任务, 异步编程模型非常有用。 但是,在C#中使用它有点困难.C#现在也支持异步语言,你可以试试CTP版本 。

试试这个

 folderPathList.ToList().ForEach(p => { ThreadPool.QueueUserWorkItem((o) => { WebRequest request = WebRequest.Create(p); request.Credentials = DefaultCredentials; request.Method = "MKCOL"; WebResponse response = request.GetResponse(); response.Close(); }); 

编辑 – 不同的webrequest方法

 folderPathList.ToList().ForEach(p => { ThreadPool.QueueUserWorkItem((o) => { using (WebClient client = new WebClient()) { client.Credentials = DefaultCredentials; client.UploadString(p, "MKCOL", ""); } }); });