Laden...

[gelöst] Brauche kleinen Denkanstoss bei einer überwachten Queue und Threading

Erstellt von Stipo vor 13 Jahren Letzter Beitrag vor 13 Jahren 2.035 Views
Stipo Themenstarter:in
699 Beiträge seit 2007
vor 13 Jahren
[gelöst] Brauche kleinen Denkanstoss bei einer überwachten Queue und Threading

Hallo zusammen,

ich bräuchte mal einen kleinen Denkanstoss von euch, wie ich das am besten umsetzen sollte.

Folgende funktion muss ich realisieren.
Ich habe einen FileSystemWatcher, der mich per Email über seine Ereignisse informieren soll. Dazu hab ich 2 Lösungsansätze, die ich mir vorstellen kann, das es damit funktioniert.
1.In der Klasse, in der ich den FileSystemWatcher instanziere und auf sein Überwachungspfad und Filter konfiguriere, gibt es eine Queue<T> in der der FileSystemWatcher seine Ereignisse schreiben kann.
Ein Thread überwacht dann die Queue<T> und holt daraus dann die Daten um damit eine Email zu generieren. Threading und Queue<T> deshalb, da sonst der FileSystemWatcher ja blockiert in der Zeit, wo die Email versendet wird, und dadurch dann FileSystem Ereignisse verloren gehen könnten.

1.Der FileSytemWatcher selbst ist in einem eigenen Thread ausgelagert und meine Klasse hat die Queue<T>. Der Thread mit dem FileSystemWatcher schreibt dann in die Queue seine Ereignisse und der Hauptthread ( also meine Klasse selbst ) überwacht dann wieder die Queue und sendet die Emails.

Welches der beiden möglichkeiten, würdet Ihr implementieren?
Oder hättet Ihr noch eine total andere Lösungsidee, wie man das umsetzt?

Bin mal über eure Meinungen gespannt. Kenne die Queue<T> noch nicht so gut, und in Threads bin ich auch noch nicht ganz so standfest, als das ich dass jetzt so direkt entscheiden vermag, welche meiner beiden Ideen die bessere lösung ist.

Abschließend, würden mich dann noch ein paar Fallen interessieren, die mir da begegnen werden, womit ich jetzt schon rechne, das ich in irgendeine davon dann fallen werde.

Danke euch schonmal.

Grüße Stephan

6.911 Beiträge seit 2009
vor 13 Jahren

Hallo,

das ist ein "klassischer" Fall von Producer/Consumer. Verwendest du .net 4.0? Dort gibt es die BlockingCollection<T> (eine threadsichere Queue) die dafür geeignet ist.
Schau dir am besten das Beispiel in dem PDF von Patterns for Parallel Programming with the .NET Framework an.

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

Stipo Themenstarter:in
699 Beiträge seit 2007
vor 13 Jahren

Hallo Gü,

ja ich nutze NET 4.0. Schaue mir dann mal die Klasse und den Link an. Hört sich zumindest schonmal interessant an.

Grüße Stephan

49.485 Beiträge seit 2005
vor 13 Jahren

Hallo Stipo,

mein Vorschlag wäre: der FSW läuft in einem Thread, die E-Mail-Erzeugung in einem anderen. Die Kommunikation läuft über eine SyncQueue <T> - Eine praktische Job-Queue. Dann musst du dich um die Synchronisation nicht mehr selber kümmern.

herbivore

Stipo Themenstarter:in
699 Beiträge seit 2007
vor 13 Jahren

Hallo herbivore,

dein Ansatz hört sich auch gut an und deine SyncQueue<T> ist ein guter Tip, den ich mir sofort fest ins Gehirn Meisel.

Hab eben einen ersten Test geschrieben, mit der BlockingCollection<T> und einem Thread, der sich um die Emails kümmert.

Im moment bin ich damit ein wenig am testen, aber werde deine Variante sicher auch mal noch durchtesten. Alleine schon um zu sehen, welche Variante eventuell schneller ist, denn die jetzige Implementation hat sehr lange laufzeiten um die Emails zu versenden, welche es aus der BlockingCollection<T> ließt.

Ansonsten scheine ich damit auf dem richtigen Weg zu sein.

Danke euch beiden noch für die guten Infos.

Grüße Stephan

PS: @Gü: Ich muss mir in ruhe mal die PDF durchlesen. Aber scheint eine interessante sache zu sein.

4.221 Beiträge seit 2005
vor 13 Jahren

Eine Lösungsansatz ohne Queue:

Im Event des FSW eine Instanz einer Klasse erstellen... dieser die Informationen des Events übergeben und dann direkt in dieser Klasse einen Thread starten der dann das Mail aufbereitet und verschickt... (Create and forget).

Gruss
Programmierhans

Früher war ich unentschlossen, heute bin ich mir da nicht mehr so sicher...

6.911 Beiträge seit 2009
vor 13 Jahren

Hallo Stephan,

als Hinweis: Die BlockingCollection<T> von .net 4.0 entspricht quasi der SyncQueu<T> von herbivore.

D.h. für beide gelten die selben Überlegungen.

mfG Gü

PS: Für das PDF hab ich absichtlich keine Seitennummer angegeben da der ganze Inhalt lesenswert ist 😉

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

Stipo Themenstarter:in
699 Beiträge seit 2007
vor 13 Jahren

Hallo zusammen,

als Hinweis: Die BlockingCollection<T> von .net 4.0 entspricht quasi der SyncQueu<T> von herbivore.

D.h. für beide gelten die selben Überlegungen.

Ich hab beide Klassen mal durchgetestet. Was Du sagst kann ich bestätigen.
Nur kann man den mechanismus dahinter besser verstehen, wobei das ja ansich nicht sonderlich spannend und umfangreich ist.

PS: Für das PDF hab ich absichtlich keine Seitennummer angegeben da der ganze Inhalt lesenswert ist 😉

Ich werde mir die PDF auch noch ausdrucken und mal in ruhe durchlesen. Da es in Englisch ist, wird das dann zwar sicher ein bisschen länger dauern, aber das Thema interessiert mich allemal, da ich jetzt ja selbst einen 4-Kern Phenom habe, mit dem ich das sogar testen kann.
Hatte gestern nämlich schon bemerkt, das wenn ich meiner Anwendung ordentlich was zum kauen gebe, der auch nur 1 Kern richtig auslastet.

Im Event des FSW eine Instanz einer Klasse erstellen... dieser die Informationen des Events übergeben und dann direkt in dieser Klasse einen Thread starten der dann das Mail aufbereitet und verschickt... (Create and forget).

Dein Vorschlag hört sich auch gut an. Hab das nun zwar schon anders gelöst, werde mir die Idee dahinter aber mal merken.

Allgemein hat mir das Thema nun sehr viel gebracht um die thematik mit den Threads besser zu verstehen. Ist eben immer besser, wenn man selbst mal vor dem Problem steht und lösen muss, als wenn man sturr Themen aus Büchern oder sonstigen Quellen nacharbeitet.

Danke nochmal an alle, die mir geholfen haben.

Grüße Stephan

4.221 Beiträge seit 2005
vor 13 Jahren

Im Event des FSW eine Instanz einer Klasse erstellen... dieser die Informationen des Events übergeben und dann direkt in dieser Klasse einen Thread starten der dann das Mail aufbereitet und verschickt... (Create and forget).
Dein Vorschlag hört sich auch gut an. Hab das nun zwar schon anders gelöst, werde mir die Idee dahinter aber mal merken.

So eine Lösung ist mit kleinem Aufwand realisierbar... allerdings ist es auch mit Vorsicht zu geniessen... denn Du gibst die volle Kontrolle an den Thread ab (Create and forget)... somit nur dann einsetzen wenn die Welt nicht untergeht wenn mal ein Mail nicht ankommt (also im Prinzip eine Schönwetterlösung).

Früher war ich unentschlossen, heute bin ich mir da nicht mehr so sicher...

Stipo Themenstarter:in
699 Beiträge seit 2007
vor 13 Jahren

Hallo zusammen,

eine kleine Frage hab ich noch, bezüglich der Zugehörigkeit der SyncQueue in der Klasse.

Folgenden Code verwende ich ( stark gekürzt ):


        internal class Watcher
        {
            private Thread _thread;
            private SyncQueue<MyObjectClass> _syncQueue;

            public Watcher()
            {
                _syncQueue = new SyncQueue<MyObjectClass>();
                _thread = new Thread(new ThreadStart(SyncQueueThread));
                _thread.Start();
            }

            private void SyncQueueThread()
            {
                while (true)
                {
                    MyObjectClass myObject = _syncQueue.Dequeue();
                    MyMail mail = new MyMail();
                    mail.Send(myObject);
                }
            }

            private void MethodeAdd()
            {
               MyObjectClass newMyObject = new MyObjectClass("Value1", Value2");
                _syncQueue.Enqueue(newMyObject);
            }
        }

Die Klasse "Watcher" läuft in dem Thread, der durch den ThreadPool des FileSystemWatcher erstellt wird. Das ist mir klar.
Doch in welchen Thread läuft da nun die SyncQueue<T> ?
Das versteh ich noch nicht ganz, bzw kann das nicht genau zuordnen.
Mich verwirrt dabei ein bisschen, da ich ja das Enqueue der SyncQueue definitiv in dem Thread des Watcher mache, aber das Deqeue dann definitiv wieder in dem Thread für die Emails.

Meine einschätzung ist, das die SyncQueue<T> komplett in dem SyncQueueThread läuft. Ich erkläre mir das damit, das innerhalb der SyncQueue<T> Klasse im Dequeue() der Thread mit Monitor.Wait(this); angehalten wird, und dann in Enqueue() mit Pulse wieder zum leben erweckt wird.
Aber der FileSystemWatcher wird dadurch nicht unterbrochen.

Grüße Stephan

6.911 Beiträge seit 2009
vor 13 Jahren

Hallo,

die SyncQueue<T> selbst läuft nirgends denn sie liegt auf dem Heap 😉
D.h. es ist ein Objekt das bestimmte Methoden hat und diese Methoden werden von den Threads ausgeführt. Da gemeinsamer Zugriff mehrerer Threads auf das Objekt erfolgt müssen diese Zugriffe eben synchronisiert werden.

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

3.971 Beiträge seit 2006
vor 13 Jahren

Warum so kompliziert? Ich muss gestehen, hab nicht alles direkt durchgelesen aber was ist mit?


class Watcher {
  
  public void AddEvent(MyObjectClass e) {
    ThreadPool.QueueUserWorkItem(ProcessEvent, e);
  }

  private void ProcessEvent(object args) {
    MyObjectClass e = (MyObjectClass) args;

    MyMail mail = new MyMail();
    mail.Send(e);
  }
}

Eleganter wäre es aber, wenn du das über Logging (log4net) machst. Dies lässt anschließend bequem über die App.Config konfigurieren. Einen fertigen SmtpAppender gibts bereits auch fertig.

Es gibt 3 Arten von Menschen, die die bis 3 zählen können und die, die es nicht können...

6.911 Beiträge seit 2009
vor 13 Jahren

Hallo,

der Vorschlag von kleines_eichhoernchen

Eleganter wäre es aber, wenn du das über Logging (log4net) machst.

gefällt mir sehr gut. Zusätzlich hast du noch den Vorteil dass du auch eine "normale" Log-File erstellen kannst oder das in einer DB mit schreiben - quasi als Backup für den Emailversand.

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

Stipo Themenstarter:in
699 Beiträge seit 2007
vor 13 Jahren

Hallo kleines_eichhoernchen,

Log4Net kenne ich noch nicht. Hab zwar schon ein paar sachen darüber gelesen, dachte aber bis jetzt, das es nur logging kann.
Das man damit auch Smtp Mails raus schicken kann, wusste ich nicht.

Ich muss in meiner Anwendung sowieso ein Logging System integrieren, da ich ja sonst keinerlei Fehler mit bekomme, die mein Windows Dienst eventuell produziert, aber derzeit will ich das mal so belassen und nicht die Log4Net möglichkeiten für den Email Versand nutzen.

Dein Beispiel entspricht nicht ganz, dem was ich mache.
Ich packe jede Email, die versendet werden soll ersteinmal in eine SyncQueue rein. Bei deinem Beispiel steckst Du das aber direkt in einen Thread, der die Email verschicken soll.

Grundlegend soll es aber so realisiert sein, das keine Email verloren gehen darf/soll. Wenn nun aber der Email Versand fehl schlägt, müsste ich bei deinem Beispiel das Email ja trotzdem noch in eine SyncQueue packen, die das solange speichert, bis die Email wieder versendet werden kann ( Netzwerk Fehler etc ).

Ich gehe in meiner implementierung nämlich bei einer Exception bei und stecke die Email erstmal zurück in die Queue.

So kann ich bei andauerndem Netzwerk Fehler zB die Queue noch weg serialisieren oder so.
Auch ist ein Problem dabei, das gerade der Versand lange andauert. Was aber wenn dann zwischenzeitlich der Dienst neu gestartet wird. Dann müsste der Dienst ja solange warten bis alle Mails draußen sind.

Das umschiffe ich durch die Queue, da ich die dann immer weg serialisieren kann, und später wieder lade.

Abschließend hab ich noch eine Frage zu deinem verwendeten ThreadPool. Ist es nicht schneller, wenn ein Thread schon existiert der nur angehalten ist, als das man jedesmal erst einen Thread anfordern muss?

die SyncQueue<T> selbst läuft nirgends denn sie liegt auf dem Heap 😉

Danke noch für die Erklärung. Das hat dann mein Knoten im Kopf gelöst 😃

Grüße Stephan

3.971 Beiträge seit 2006
vor 13 Jahren

Grundlegend soll es aber so realisiert sein, das keine Email verloren gehen darf/soll.

Dann mach das lieber in einem seperatem Prozess/Dienst. Damit ist auch gewährleistet, dass das Abstürzen/Beenden des eigentlichen Prozesses erkannt wird.

Wenn nun aber der Email Versand fehl schlägt, müsste ich bei deinem Beispiel das Email ja trotzdem noch in eine SyncQueue packen, die das solange speichert, bis die Email wieder versendet werden kann ( Netzwerk Fehler etc ).

Bei Log4net (bei anderen Logging-Systemen sicherlich ähnlich) kannst du mehrere Appender via App.Config angeben. So könnten Exceptions in einer Datei, per Email und auch in einer Datenbank zeitgleich geloggt werden. Es ist auch sehr leicht einen eigenen Appender zu schreiben, der wie in deinem Fall mehrmals versucht die Email zu versenden.

So kann ich bei andauerndem Netzwerk Fehler zB die Queue noch weg serialisieren oder so.

Würdest du also nicht brauchen, wenn du das mit log4net machst und mehrere Appender benutzt.

Abschließend hab ich noch eine Frage zu deinem verwendeten ThreadPool. Ist es nicht schneller, wenn ein Thread schon existiert der nur angehalten ist, als das man jedesmal erst einen Thread anfordern muss?

Die Erstellung von Threads ist relativ teuer. Der Threadpool hält bereits mehrere gestartete Threads bereit, die auf Arbeit warten und nach Beendigung der Arbeit wieder zurück in den Pool wandern. Besonders bei kleinen und mittleren Aufgaben ist der Threadpool immer die bessere Wahl.

Eigene Threads sollten nur für lange Aufgaben/Berechnungen benutzt werden und für Aufgaben die prioritisiert werden sollen (Threadpriorität). Ein weiteres Beispiel COM-Klassen. Diese müssen meist immer mit dem selben Thread angesprochen werden.

Es gibt 3 Arten von Menschen, die die bis 3 zählen können und die, die es nicht können...

49.485 Beiträge seit 2005
vor 13 Jahren

Hallo kleines_eichhoernchen,

Besonders bei kleinen und mittleren Aufgaben ist der Threadpool immer die bessere Wahl.

... die bessere Wahl, als für jede Aufgabe einzeln immer wieder einen neuen Thread zu starten. Das passiert ja aber nicht, wenn man die SyncQueue verwendet. Der oder die Threads, die die SyncQueue abarbeiten, werden üblicherweise nur einmal gestartet und laufen dann die ganze Zeit. Dadurch gibt es keinen Nachteil gegenüber dem ThreadPool.

Die Vorteile der SyncQueue sind zu einen, dass man mehr Kontrolle hat. Bei QueueUserWorkItem erfolgt z.B. die Abarbeitung der einzelnen Aufgaben potenziell parallel. Das kann natürlich gewünscht sein, es kann aber auch stören, weil man dann die Aufgaben möglicherweise gegeneinander synchronisieren muss. Wenn es gewünscht ist, dann kann man auch bei der SyncQueue einfach (einmalig) mehrere Threads erzeugen, die alle an derselben SyncQueue lauschen.

Außerdem gibt es bei ThreadPool - wenn ich mich nicht irre - nur einen Pool (pro Prozess). Wenn man unterschiedliche Aufgaben hat, kommen die allen in einen Pool und werden bei der Abarbeitung ebenfalls lustig gemischt.

Wenn man SyncQueue verwendet, kann man direkt steuern, wie viele Thread es geben soll und vor allem, welcher Thread welche Aufgaben verarbeiten soll. Insbesondere kann man bestimmen, dass nur ein bestimmter Thread bestimmte Aufgaben verarbeitet. Diese Aufgaben werden dann zwar parallel zum Einstellen in die SyncQueue verarbeitet, aber eben nicht untereinander parallel.

Außerdem wird bei QueueUserWorkItem nur der Code ausgeführt, den der Auftraggeber explizit angibt. Überhaupt ist das eine "lowlevel" Art, Aufträge zu erteilen. Man sagt, führe diesen oder jenen Code aus. Das gibt dem Auftraggeber zwar die volle Kontrolle, aber das ist nicht immer, was man will. Manchmal möchte man, dass der Auftragnehmer die volle Kontrolle hat. Bei einer SyncQueue kann man die Aufgaben in beliebiger Form definieren, also auch als eigene Klassen. Der Worker-Thread kann dann seine Aufträge aus der SyncQueue so interpretieren, wie das sinnvoll ist. Insbesondere kann er selbst bestimmen, welcher Code ausgeführt wird und er kann z.B. zwischen den Aufträgen auch zusätzlichen Code ausführen, der nicht direkt einem Auftrag zugeordnet ist.

Dass man bei der SyncQueue auch leicht, um nicht zu sagen automatisch, sicherstellen kann, dass Zugriffe auf die von dir angesprochenen COM-Komponenten immer aus demselben Thread erfolgen, ist ein weiterer Vorteil.

Sicher, auch mit dem ThreadPool bekommt man das alles irgendwie hin. Aber man muss dazu erst noch Abstraktionsschichten einziehen oder zusätzlichen Aufwand treiben, den man sich bei der SyncQueue sparen kann. Also gerade wenn man SyncQueue und ThreadPool vergleicht (und nicht einfach nur Threads und ThreadPool), kann ich nicht finden, dass da ein ThreadPool da irgendwo überlegen ist, ganz im Gegenteil.

herbivore

6.911 Beiträge seit 2009
vor 13 Jahren

Hallo herbivore,

Außerdem gibt es bei ThreadPool - wenn ich mich nicht irre - nur einen Pool (pro Prozess).

Ist so nicht ganz korrekt. Bis .net 2.0 gab es 2 Pools: 1 für die Arbeiter-Threads und 1 für die IO-Threads. Ab .net 3.5 gabs einen Pool pro AppDomain.

Ab .net 4.0 gibt es einen überarbeiteten "ThreadPool". Der IO-ThreadPool blieb unverändert.
Bisher war die Queue nach FIFO implementiert und war ein einfache Linked-List die mit einem Monitor synchronisiert wurde. Neu gibt es einen globalen Pool der ConcurrentQueue<T> als Queue für die Threads verwendet. Diese ist "lock frei" und somit ist der Overhead für QueueUserworkItem geringer.
Zusätzlich gibt es pro Thread einen Pool damit "work stealing" ermöglicht wird. Dieser Pool ist als ConcurrentBag<T> umgesetzt. D.h. wenn ein Thread für Arbeit sucht wird zuerst sein lokaler Pool durchsucht, wenn dieser leer ist der globale Pool und wenn dieser auch leer ist der Pool von anderen Threads -> work stealing.

mfG Gü

Speedski, ThreadPool

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

3.971 Beiträge seit 2006
vor 13 Jahren

Besonders bei kleinen und mittleren Aufgaben ist der Threadpool immer die bessere Wahl.
... die bessere Wahl, als für jede Aufgabe einzeln immer wieder einen neuen Thread zu starten.

Die SyncQueue-Klasse betrachte ich persönlich auch wiederum als eine eigene Threadpool-Implementierung. Die Klasse macht nix anderes als ihr hinzugefügte Aufgaben auf ein oder mehrere Threads zu verteilen. Nach der Ausführung kehrt der oder die Threads wieder zurück und holt sich die Nächste. Also das Gleiche wie der Threadpool auch, nur halt eine andere Implementierung.

Ob man jetzt nun den System.Threading.Threadpool, Smart Thread Pool oder deine SyncQueue-Klasse benutzt, ist prinzipiell egal - es kommt halt auf die Anwendung/Zweck drauf an.

Es ist nur in meinen Augen absolut Schwachsinn, für Aufgaben wie "ich hol mir mal was aus der Datenbank" einen seperaten Thread zu erstellen, damit die Gui blockiert. Dafür gibt es Threadpools.

Es gibt 3 Arten von Menschen, die die bis 3 zählen können und die, die es nicht können...

49.485 Beiträge seit 2005
vor 13 Jahren

Hallo kleines_eichhoernchen,

Die Klasse macht nix anderes als ihr hinzugefügte Aufgaben auf ein oder mehrere Threads zu verteilen.

nö, die Klasse stellt erstmal nur eine synchronisierte Queue bereit. Die Verteilung auf Threads liegt in der Hand des Benutzers der SyncQueue. Normalerweise wird man es so handhaben, dass während der Abarbeitung der Aufträge keine neuen Thread gestartet werden.

Nach der Ausführung kehrt der oder die Threads wieder zurück und holt sich die Nächste.

Insofern kehrt auch kein (anderer) Thread zurück, sondern der oder die Threads, deren Aufgabe es ist, die SyncQueue abzuarbeiten, holen einfach in einer Schleife einen nach dem anderen Auftrag aus der Queue und führen die Aufträge selber aus.

Also das Gleiche wie der Threadpool auch, nur halt eine andere Implementierung.

Mit der SyncQueue kann man das gleiche machen, wie mit einem ThreadPool, nur dass man bei der SyncQueue eben die beschriebenen Vorteile hat. 😃

Es ist nur in meinen Augen absolut Schwachsinn, für Aufgaben wie "ich hol mir mal was aus der Datenbank" einen seperaten Thread zu erstellen, damit die Gui blockiert.

Du meinst jedesmal wieder einen neuen Thread zu erstellen, oder? Da sind wir uns ja einig. Genau das kann man mit der SyncQueue vermeiden.

herbivore