Announcement

Collapse
No announcement yet.

Mail senden aus einer parallel.foreach Schleife

Collapse
X
  • Filter
  • Time
  • Show
Clear All
new posts

  • Mail senden aus einer parallel.foreach Schleife

    Hallo Profis!

    Folgende Frage: Ich versuche in einem Windows-Service aus einer parallel.foreach - Schleife heraus Mails zu versenden: Template als string übergeben, Platzhalter wie Vor- und Nachnamen etc. ersetzen und dann per zugekaufter Komponente (die aber meiner Meinung nach eine Erweiterung der .net-eigenen Mailklassen ist, habs btw. auch per System.Net.Mail versucht) an das Windows-Smtp-Service zu senden (ein SmtpClient pro Thread).
    Mein Problem ist nun, dass ich irgendwie keine großartige Geschwindigkeitssteigerung gegenüber einer normalen foreach-Schleife bekommen habe (ca. 10% Unterschied wenn überhaupt, hab das mal testweise per paralleloptions-Attribut auf einen Thread beschränkt). Der einzige großartige Unterschied war eine (viel) geringere Prozessorlast.
    Meine Vermutung wäre nun, dass das Windows-Smtp-Service nur sequentiell beschickt werden kann und jeder Thread warten muss, bis der vorherige das Senden beendet hat (so könnte ich mir zumindest den leichten Geschwindigkeitsanstieg erklären).
    Nun die eigentliche Frage: liege ich mit meiner Vermutung richtig oder muss ich da doch woanders weitersuchen? Und wenn ja, gibts da einen Ansatz, der mir bei der Lösung des Problems helfen könnte?

    Theoretisch ist ja mein eigentliches Problem eh das Smtp-Service an sich, das sehr viel langsamer versendet als die Mails generiert werden. Allerdings steigt die Versandgeschwindigkeit natürlich erheblich, wenn das generieren abgeschlossen ist, was mir ingesamt wieder einen Zeitvorteil bringt.


    Danke schonmal jetzt für eure Inputs!
    lg, Christian

  • #2
    Hallo,

    Template als string übergeben, Platzhalter wie Vor- und Nachnamen etc. ersetzen
    Ich weiß nicht von welchem Template du jetzt sprichts, aber hierzu finde ich T4 ganz praktisch. Siehe hierzu Generating Text Files at Run Time by Using Preprocessed Text Templates.

    dass ich irgendwie keine großartige Geschwindigkeitssteigerung gegenüber einer normalen foreach-Schleife bekommen habe
    Bevor ich weitere Möglichkeiten nenne kurz ein kleiner Hintergrund wie das mit Threads läuft.
    Es gibt zwei "Arten" von Threads im ThreadPool. Zum Einen die CPU-Thread und zum Anderen die I/O-Abschlussthreads. Letztere sind generell für I/O-Aufgben zu bevorzugen d.h. die Async-Methode zu verwenden denn dabei werden keine CPU-Resourcen benötigt und erst wenn die asynchrone Operation (in den Sockets zB) fertig ist wird aus dem Thread-Pool ein I/O-Abschlussthread verwendet und auch erst dann wird die CPU benötigt.

    So die Möglichkeiten die mir einfallen wie das zu beschleunigen wäre sind
    • Parallel.ForEach
    • Async-Methoden
    • PLinq

    Ich hab für den WebClient mal so einen Vergleich gebastelt und da sind die async-Methoden klar im Vorteil. Siehe Aufgaben werden trotz ThreadPool scheinbar nacheinander ausgeführt. In diesem Beispiel hab ich zwar die parallel Foreach-Schleife primitiv nachgebaut, aber das ändert nichts am Verhalten. Du kannst dir diesen Code als Vorlage verwenden und anpassen. Für die async-Operation hat die Smtp-Klasse die SendAsync-Methode.
    Für Paralle.Foreach und PLinq siehe When Should I Use Parallel.ForEach? When Should I Use PLINQ?

    das Windows-Smtp-Service nur sequentiell beschickt werden kann und jeder Thread warten muss
    Meinst du mit dem Windows-Smtp-Service den IIS Smtp Service? Wenn ja dann ist das nicht so. Der Standardwert für ausgehende Verbindungen ist 1000. Siehe Einrichten von Verbindungen.

    das sehr viel langsamer versendet als die Mails generiert werden
    Ich weiß nicht wie du die generierten Emails an den "Sender" übergibts, aber ich würde das über einen Producer/Consumer erledigen. Da du von Parallel.Foreach geschrieben hast gehe ich von .net 4.0 aus und dort gibt es dafür die BlockingCollection<T>. Siehe auch Blocking Collection and the Producer-Consumer Problem.

    D.h. ich würde im Producer die Emails per T4 generieren und dann in den Buffer (BlockingCollection) schreiben. Der Consumer liest die Emails und sendet diese Async - auf das Ende des Sendevorgangs muss nicht (unbedingt) gewartet werden, außer auf die letzte Email damit der Vorgang korrekt abgeschlossen werden kann bzw. um Fehler zu berichten, etc.

    Probier die Möglichkeiten mal aus und poste dann das Ergebnis bitte hier - Danke.


    mfG Gü
    "Any fool can write code that a computer can understand. Good programmers write code that humans can understand". - Martin Fowler

    Comment


    • #3
      Hallo gfoidl!

      Ein riesen Dankeschön jetzt schonmal für die sehr ausführliche Antwort, ich werd das alles mal durchackern und geb dann Bescheid. Wird halt nur ein bissl dauern aber ich werd gern meine Erkenntnisse hier posten.


      lg, Christian

      Comment


      • #4
        Ach ja, noch was: mein "Template" ist ein einfacher string, den ich in eine Xml-Struktur umwandle und dann parse, das ist aber auch nicht das Problem, geht eigentlich ganz fix.

        Wegen "an den Sender übergeben": eigentlich rufe ich ganz einfach in der parallel.foreach die Methode SmtpClient.Send(MaiMessage) auf.

        Mit dem IIS Smtp Service hast du recht, war von mir falsch benamselt ;-) ich hab aber eigentlich nicht das Versenden der Mails nach draussen gemeint, sondern wenn ich die Send(...) Methode aufrufe, wird die Mail ja vom Smtp-Service "entgegengenommen" und in die Queue geschmissen. Ich meinte, ob dieses Entgegennehmen vielleicht nur sequentiell geht.

        lg, Christian

        Comment


        • #5
          Hallo,

          mein "Template" ist ein einfacher string
          Schon klar dass das nicht das Bottle-neck sein wird, ich wollte dir nur eine nette Alternative zeigen.

          Wegen "an den Sender übergeben"
          Ich hab eher an so was gedacht - rein schematisch:
          [highlight=c#]
          public class EmailProcessor
          {
          private BlockingCollection<MailMessage> _buffer;

          // Producer
          public void CreateEmails(IEnumerable<EmailData> emialData)
          {
          Task.Factory.StartNew(() =>
          {
          foreach (EmailData data in emailData)
          {
          MailMessage email = CreateEmail(data);
          _buffer.Add(email);
          }

          _buffer.CompletedAdding();
          });
          }

          // Consumer
          public void SendEmails()
          {
          Task.Factory.StartNew(() =>
          {
          SmtpClient smtp = new SmtpClient();

          foreach (MailMessage email in _buffer.GetConsumingEnumerable())
          {
          smtp.SendAsync(email) // oder was sich halt als schneller herausstellt.
          }
          });
          }
          }
          [/highlight]


          IIS Smtp Service
          Wie es tatsächlich ist weiß ich nicht, aber ich nehme es ist wie in der SmtpClient.Send-Methode beschrieben.

          mfG Gü
          "Any fool can write code that a computer can understand. Good programmers write code that humans can understand". - Martin Fowler

          Comment


          • #6
            Hi gfoidl!

            Originally posted by gfoidl View Post
            Schon klar dass das nicht das Bottle-neck sein wird, ich wollte dir nur eine nette Alternative zeigen.
            Ja, danke, habs mir auch schon angesehen und gefällt mir auch sehr gut. Wenn noch Zeit bleibt, werd ichs damit mal probieren.


            Originally posted by gfoidl View Post
            Ich hab eher an so was gedacht - rein schematisch:
            Das versuch ich grad genauso umzusetzen, wie in dem Blog, das du mir ans Herz gelegt hast - den Producer hab ich mal und nach einem ersten Test kann ich sagen: wenn ich das Send() weglasse un die Mails nur in die BlockingCollection generiere dauerts lt. EventLog nicht mal eine Minute im Gegensatz zu über einer Stunde mit Send() :-)



            Originally posted by gfoidl View Post
            Wie es tatsächlich ist weiß ich nicht, aber ich nehme es ist wie in der SmtpClient.Send-Methode beschrieben.
            Hab ich mir grad angesehen und ich glaube der Satz This method blocks while the e-mail is transmitted bestätigt mehr oder weniger meine Theorie :-( Aber Ich probiers mal mit SendAsync, vielleicht klappts damit besser ...
            Eine Frage hab ich dazu aber noch: Bei meinem letzten Versuch (sowohl mit Send() als auch mit SendAsync()) hab ich versucht, für die gesamte Parallel.ForEach-Schleife einen einzigen SmtpClient zu verwenden, was dazu geführt hat, dass bei fast allen Durchläfen eine Exception geschmissen wurde, weil ein vorheriger Sende-Vorgang noch nicht abgeschlossen ist. Das hab ich dann über einen RangePartitioner gelöst, bei dem innerhalb der Parallel.ForEach-Schleife für jede range eine normale foreach-Schleife gelaufen ist. Pro innerer Schleife gibts jetzt einen eigenen SmtpClient, was mir aber auch nicht gefällt, weil ja erst bisschen Performance flöten geht. Kann man das irgendwie eleganter lösen?

            lg, Christian

            Comment


            • #7
              Hallo,

              was dazu geführt hat, dass bei fast allen Durchläfen eine Exception geschmissen wurde
              eben genau wegen
              This method blocks while the e-mail is transmitted
              RangePartitioner gelös
              Schau dir mal das PDF im oberen Beitrag an - das ist ein Bsp. mit einer Überladng von Parallel.Foreach die zeigt wie es besser geht.


              mfG Gü
              "Any fool can write code that a computer can understand. Good programmers write code that humans can understand". - Martin Fowler

              Comment

              Working...
              X