外部の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)
作成する接続が多すぎるため、使用できるすべてのローカルポートが使い果たされる可能性があります。ポートを閉じた後、ポートを再利用できるタイムアウト期間があります。 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つは、動的ポート範囲を拡大することです。デフォルトの最大値は5000です。これは65534まで設定できます。HKLM\System\CurrentControlSet\Services\Tcpip\Parameters\MaxUserPort
は使用するキーです。
次にできることは、接続がTIME_WAIT状態になったら、その状態にある時間を短縮できます。デフォルトは4分ですが、これを30秒に設定できますHKLM\System\CurrentControlSet\Services\Tcpip\Parameters\TCPTimedWaitDelay
は使用するキーです。これを30秒に設定します
接続が不必要に長く開かれる原因となる可能性のある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を追加して接続を再試行する必要があり、タイムアウトするとコードがより複雑になる可能性があります。
この種の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", "");
}
});
});