web-dev-qa-db-ja.com

C#で多数のWebリクエストをマルチスレッド化する

外部のSharePointサイトに多数のフォルダーを作成する必要があるプログラムがあります(外部とは、SharePointオブジェクトモデルを使用できないことを意味します)。これにはWeb要求が適切に機能しますが、一度に1つずつ実行する(要求の送信、応答の待機、繰り返し)だけではかなり時間がかかります。リクエストをマルチスレッド化して、スピードアップすることにしました。プログラムは大幅に高速化されましたが、しばらくすると(1〜2分程度)、同時実行例外がスローされ始めます。

コードは以下にあります、これはこれを行うための最良の方法ですか?

Semaphore Lock = new Semaphore(10, 10);
List<string> folderPathList = new List<string>();
//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();
}

例外は、

未処理の例外:System.Net.WebException:リモートサーバーに接続できません---> System.Net.Sockets.SocketException:通常、各ソケットアドレスの使用は1回のみ許可されます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)

23
Terry

作成する接続が多すぎるため、使用できるすべてのローカルポートが使い果たされる可能性があります。ポートを閉じた後、ポートを再利用できるタイムアウト期間があります。 WebRequestは、すべての低レベルのソケット処理を非表示にしますが、最終的にはポートが不足するか、すでにTIME_WAIT状態にあるソケットに(再)バインドしようとしていると思います。

応答を気にしない場合でも、応答ストリームを必ず読んでください。これは、長引く接続が多すぎないようにするのに役立ちます。

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

ここ からいくつかの関連情報を貼り付けます:

接続が閉じられると、接続を閉じる側で、5タプル{プロトコル、ローカルIP、ローカルポート、リモートIP、リモートポート}がデフォルトで240秒間TIME_WAIT状態になります。この場合、プロトコルは固定されています-TCPローカルIP、リモートIP、およびリモートPORTも通常は固定されています。したがって、変数はローカルポートです。そうでない場合はどうなりますか?バインドでは、1024〜5000の範囲のポートが使用されます。つまり、およそ4000個のポートがあります。4分ですべてを使用すると、つまり、1秒あたり16回のWebサービス呼び出しを4分間行うと、すべてのポートが使い果たされます。これがこの例外の原因です。

では、どうすればこれを修正できますか?

  1. 方法の1つは、動的ポート範囲を拡大することです。デフォルトの最大値は5000です。これは65534まで設定できます。HKLM\System\CurrentControlSet\Services\Tcpip\Parameters\MaxUserPortは使用するキーです。

  2. 次にできることは、接続がTIME_WAIT状態になったら、その状態にある時間を短縮できます。デフォルトは4分ですが、これを30秒に設定できますHKLM\System\CurrentControlSet\Services\Tcpip\Parameters\TCPTimedWaitDelayは使用するキーです。これを30秒に設定します

21
nos

接続が不必要に長く開かれる原因となる可能性のあるWebリクエストを閉じていません。これは、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());
            }
        });

覚えておくべきもう1つのことはmaxConnectionsです。必ずapp.configで設定してください。

<configuration>
  <system.net>
    <connectionManagement>
      <add address = "*" maxconnection = "100" />
    </connectionManagement>
  </system.net>
</configuration>

実際のシナリオでは、try-catchを追加して接続を再試行する必要があり、タイムアウトするとコードがより複雑になる可能性があります。

10
Homde

この種のIO集中的なタスクの場合、 非同期プログラミングモデル は非常に便利です。ただし、C#で使用するのは少し難しいです。C#では言語レベルのサポートもあります。非同期になりました。 CTPリリース を試すことができます。

2
Yin Zhu

これを試して

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", "");
                     }
                 });
        });
1
Dean Chalk