25 Stimmen

Multithreading einer großen Anzahl von Webanfragen in c#

Ich habe ein Programm, wo ich einige große Anzahl von Ordnern zu einer externen Sharepoint-Website (extern bedeutet, ich kann nicht das Sharepoint-Objektmodell verwenden) erstellen müssen. Web-Anforderungen funktionieren gut für diese, aber einfach tun sie eine zu einer Zeit (Anfrage senden, warten auf Antwort, wiederholen) ist ziemlich langsam. Ich beschloss, die Anfragen zu multithreaden, um zu versuchen, sie zu beschleunigen. Das Programm hat sich erheblich beschleunigt, aber nach einer gewissen Zeit (zwischen 1-2 Minuten oder so), Gleichzeitigkeit Ausnahmen beginnen immer ausgelöst.

Der Code steht unten, ist das der beste Weg, dies zu tun?

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();
}

Die Ausnahme ist etwas in der Art von

Unbehandelte Exception: System.Net.WebException: Verbindung zum entfernten Server kann nicht hergestellt werden ---> System.Net.Sockets.SocketException: Normalerweise ist nur eine Verwendung jeder Socket-Adresse erlaubt 192.0.0.1:81
at System.Net.Sockets.Socket.DoConnect(EndPoint endPointSnapshot, SocketAddre ss socketAddress)
at 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)

22voto

nos Punkte 214143

Sie könnten zu viele Verbindungen herstellen und damit alle lokalen Ports, die Sie verwenden können, belegen. Es gibt eine Zeitspanne, in der ein Anschluss wieder verwendet werden kann, nachdem Sie ihn geschlossen haben. WebRequest verbirgt die gesamte Socket-Verarbeitung auf niedriger Ebene für Sie, aber ich vermute, dass ihm irgendwann die Ports ausgehen oder er versucht, sich (erneut) an einen Socket zu binden, der sich bereits in einem TIME_WAIT-Zustand befindet.

Sie sollten sicherstellen, dass Sie den Antwortstrom lesen auch wenn Sie sich nicht um die Antwort kümmern. Dies sollte dazu beitragen, dass nicht zu viele verbleibende Verbindungen entstehen.

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

Ich füge einige relevante Informationen aus aquí :

Wenn eine Verbindung geschlossen wird, wird auf der Seite, die die Verbindung schließt, das Tupel 5 { Protokoll, Lokale IP, Lokaler Port, Entfernte IP, Entfernter Port} standardmäßig für 240 Sekunden in den Zustand TIME_WAIT. In diesem Fall ist das Protokoll festgelegt - TCP die lokale IP, die entfernte IP und der entfernte PORT sind normalerweise ebenfalls fest. Die Variable ist also der lokale Port. Wenn Sie sich nicht binden, wird ein Port im Bereich 1024-5000 verwendet. Sie haben also ungefähr 4000 Ports. Wenn Sie alle in 4 Minuten verwenden - was bedeutet, dass Sie ungefähr 16 Webdienstaufrufe pro Sekunde für 4 Minuten, dann sind alle Ports erschöpft. Das ist die Ursache für diese Ausnahme.

OK, wie kann dies nun behoben werden?

  1. Eine Möglichkeit besteht darin, den dynamischen Portbereich zu vergrößern. Standardmäßig beträgt der Höchstwert 5000. Sie können ihn auf bis zu 65534 einstellen. HKLM\System\CurrentControlSet\Services\Tcpip\Parameters\MaxUserPort ist der zu verwendende Schlüssel.

  2. Zweitens können Sie, sobald die Verbindung in einen TIME_WAIT-Zustand gerät, die Zeit, in der sie sich in diesem Zustand befindet, reduzieren. Standardmäßig sind das 4 Minuten, aber Sie können diese Zeit auf 30 Sekunden setzen. HKLM\System\CurrentControlSet\Services\Tcpip\Parameters\TCPTimedWaitDelay ist die zu verwendende Taste. Stellen Sie dies auf 30 Sekunden ein

10voto

Homde Punkte 4168

Sie schließen die Webanforderung nicht, was dazu führen kann, dass die Verbindung unnötig lange offen bleibt. Das klingt nach einer perfekten Aufgabe für Parallel.Net's Parallel.Foreach, stellen Sie nur sicher, dass Sie angeben, auf wie vielen Threads Sie es laufen lassen wollen

  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());
            }
        });

Eine weitere Sache, die Sie beachten sollten, ist maxConnections, stellen Sie sicher, dass Sie es in Ihrer app.config einstellen:

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

Natürlich müssten Sie in einem realen Szenario try-catch hinzufügen und Verbindungen erneut versuchen, was zu einem komplizierteren Code führen könnte

2voto

Yin Zhu Punkte 16798

Für diese Art von IO-intensiven Aufgaben, asynchrones Programmiermodell ist sehr nützlich. Allerdings ist es ein wenig schwer, in C# zu verwenden.C# hat auch Sprachebene Unterstützung für async jetzt, können Sie versuchen, die CTP-Freigabe .

1voto

Dean Chalk Punkte 19313

Versuchen Sie dies

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

EDIT - anderer Webrequest-Ansatz

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

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X