Benutzer mit den meisten Antworten
Thread scheint eingefroren und "erwacht" zu spät

Frage
-
Hallo,
ich habe ein etwas kurioses Problem, das ich selbst nicht nachstellen kann. Ich weiß aber nicht wirklich, in welche Richtung ich gehen kann bzw. welchen Ansatz ich zur Problemlösung wählen soll.
Also: Unsere Anwendung teilt sich in eine Client- und eine Serverkomponente auf. Ein laufender Client meldet sich in einem Thread alle 15 Sekunden beim Server. Passiert das aber mehr als 30 Sekunden lang nicht, wird der zu dieser Clientsession gehörende User abgemeldet. Als es das noch nicht gab, konnte sich der User z.B. nach einem Clientabsturz nicht erneut anmelden bis ihn jemand manuell abmeldete. Das zum Hintergrund, was eigentlich geschehen soll. Ich habe diese Mimik sehr extrem getestet. Ich habe den Client quasi unter Volllast laufen lassen, denn kompletten Rechner unter Volllast gesetzt, sodass der komplett unbedienbar wurde - der Thread hat klaglos einen Dienst verrichtet (gut, als der Rechner unter Vollast lief hat es mal 20, vielleicht 21 Sekunden gedauert bis der nächste Serveraufruf durchgeführt wurde - aber dass er auch nur in die Nähe von 30 Sekunden kam, war beim besten Willen nicht zu provozieren). Trotzdem bekomme ich von verschiedenen Stellen die Meldung, dass die User scheinbar grundlos abgemeldet werden, obwohl der Client läuft. Das scheint vornehmlich dann der Fall zu sein, wenn man längere Zeit nicht damit arbeitet (ob in dieser Zeit überhaupt mit dem Rechner gearbeitet wird, kann ich noch nicht sagen. Es ist aber ausgeschlossen, dass der Rechner z.B. in den Standby geht).
Hier ist der relevante Code des Threads:private void RunThread() { mSession = new Session(); AutoResetEvent resetEvent = new AutoResetEvent(false); mTimer = new Timer(ResetTimer, resetEvent, 0, 15000); while (mRunning) { try { resetEvent.WaitOne(); } catch (ThreadInterruptedException) { if (!mRunning) { mTimer.Dispose(); continue; } } } } private void ResetTimer(object state) { try { mSession.ResetTimer(); IGFTrace.Instance.TraceInformation("SessionNotifierThread: Timer reset at " + DateTime.Now.ToString()); } catch (Exception ex) { IGFTrace.Instance.TraceError("SessionNotifierThread: Exception caught at " + DateTime.Now.ToString() + " " + ex.Message); mRunning = false; } if (state is AutoResetEvent) { (state as AutoResetEvent).Set(); } }
Ich habe dann mal das Tracing aktivieren lassen. Demnach hat der das 3 1/2 Stunden sehr zuverlässig durchgeführt.
Dann gibt es plötzlich eine Pause von 75 Sekunden und dann werden die 4 fehlenden Aufrufe von ResetTimer() in derselben Sekunde nachgeholt. Natürlich ist der User aber dann schon abgemeldet...
Gerade erst fällt mir auf, dass der Thread sich ja nach der ersten Exception eigentlich beenden sollte - was er aber nicht tut. Ist aber auch nur ein Nebenkriegsschauplatz.
Wenn mir jemand einen Tipp geben könnte, in welche Richtung ich da ermitteln könnte, wäre ich sehr dankbar.
Ich hatte schon mal die Garbage Collection im Verdacht. Die stoppt ja mitunter alle Threads - aber solange?
Danke und Gruß
Detlef
Antworten
-
Hallo Detlef,
Es gibt in der Anwendung keinen Threadpool.
Doch: Die Callback-Methode die System.Threading.Timer aufruft wird - wie bereits erwähnt - immer auf einem gerade freien Threadpoolthread aufgerufen (s. Dokumentation).Alle 15 Sekunden also wird im Normalfall die Methode ResetTimer() auf einem anderen Thread ausgeführt als der auf dem der Timer instanziert wurde. Du kannst das sehr einfach überprüfen, indem Du in der Konsole System.Threading.Thread.CurrentThread.ManagedThreadId ausgibst.
Das unterschiedliche Verhalten hat auch mit den einzelnen Rechnern zu tun, schließlich haben diese verschiedene Prozessoren mit einer unterschiedlichen Anzahl Kerne und mit unterschiedlichen Belastungen. Aber das Hauptproblem bleibt der Code, der so nicht zuverlässig funktionieren kann.
Gruß
Marcel- Als Antwort markiert Detlef Ernst Mittwoch, 2. Oktober 2013 09:57
Alle Antworten
-
Hi Detlef,
ich will dir ja nicht zu nahe treten, aber ist das nicht etwas umständlich, was du da machst?Das hier tut das gleiche und ist weniger als die hälfte des Codes:
Ich hoffe das hilft dir etwas weiter...private void RunThread() { //..more code while (true) { try { Thread.CurrentThread.Join(15000); //Current Thread waits 15 seconds //Then ask the server to keep the Session } catch { break; //leave the while loop if some exception was thrown } } //..more code }
Viele Grüße Holger M. Rößler
-
Hallo Holger,
ich bin da nicht ganz Deiner Meinung. Denn die Zeitangabe der Thread.Sleep() bzw. Thread.Join()-Methoden bedeuted lediglich, dass der Thread mindestens so lange anhalten soll. Es darf aber auch länger dauern. Eben jenachdem was gerade so los ist. In meinem "Volllast-Test" habe ich noch einen Sleep() benutzt. Und da habe ich ja "Verspätungen" von mehreren Sekunden festgestellt. Timer sollen da etwas verlässlicher sein.
Aber ob Timer oder Sleep() ist ja nicht mein Problem (und somit leider auch keine Antwort auf meine Frage)... -
Hallo Detlef,
In der Methode RunThread() verwendest Du einen System.Threading.Timer, der in einem Feld auf Klassenebene als Referenz gecacht wird. Jeder neue Aufruf von RunThread() überschreibt das Feld und die alte Referenz kann vom GC eingesammelt werden, auch bei aktivem Timer.
Wahrscheinlich verläßt Du dich auf mRunning um dafür zu sorgen, dass die Ausführung RunThread() nur dann verläßt, wenn die Variable auf false gesetzt wird. Aber der Zugriff auf mRunning aus einer Methode auf einem Threadpool-Thread ist fehlerträchtig und es kann zu einem kritischen Wettlauf auf die gemeinsam verwendete Ressource kommen, wenn die Methode gleichzeitig aufgerufen wird, da in der Methode keine Thread-Synchronisation (sondern lediglich Signalisierung) verwendet wird.
Zu den Timern im allgemeinen noch: Windows ist kein RTOS, also können was die Zeit angeht keine Garantien zugesichert werden. Wenn einer der Threads oder eine Drittanwendung (oder der Server GC) die CPU voll auslastet, dann können die Callbacks nicht zeitgemäß aufgerufen werden. Wenn der Windows Scheduler dann die Zeitquanten an den Prozess zuteilt und dieser wieder arbeiten kann, dann werden u.U. die Callbacks fast zeitgleich ausgeführt.
Dies kann auch passieren, wenn der Threadpool ausgelastet ist und neue Threads in eine Warteschlange kommen.
Ich denke deshalb, dass Du um eine Neustrukturierung deines Codes und um Threadsynchronisation (evtl. mit Timeouts) nicht umhin kommst. Ich weiss nicht, was genau dein Code tun soll, deshalb kann ich an dieser Stelle auch nicht weitere Empfehlungen aussprechen.
Gruß
Marcel -
Hallo Marcel,
danke für die Antwort - aber es ist NICHT der Code, welcher mir in irgendeiner Weise Probleme macht. Dieser Thread wird nur ein einziges Mal erzeugt und soll solange laufen wie das Programm lebt (oder aber eben eine Exception auftritt). Von daher ist das schon der (bis auf z.B. die Deklaration von mRunning) der ganze Code. Nachdem mein eigenltiches Problem aufgetaucht war, hatte ich lediglich Thread.Sleep() durch einen Timer ersetzt, da diese "robuster" sind. Allerdings hat das (leider) nicht dazu geführt, dass das Problem nicht mehr auftritt. Es ist einfach so - vorausgesetzt die Problemberichte sind einigermaßen der Wahrheit entsprechend -,
dass das von mir oben beschriebene Problem nach einiger Zeit des Nichtstuns auftritt. Mir war es jedenfalls nicht möglich dieses Verhalten nachzustellen. Jedenfalls nicht mit Vollast des Rechners. Eigentlich kann es nur eine Garbage Collection (oder etwas ähnliches) sein, da nur dabei die Privilegien hoch genug sein dürften, um Threads anzuhalten. Aber 75 Sekunden lang? Auch das habe ich nicht hinbekommen.
Eigentlich will ich mich ja "nur" alle 15 Sekunden bei dem Applikationsserver melden. Alle 30 Sekunden MUSS ich.
Das heißt: Ich habe 15 Sekunden Sicherheit drin. Ich kann mir einfach beim besten Willen nicht vorstellen, dass es eine Situation gibt, wo das nicht ausreichend sein soll.Gruß
Detlef
-
Hallo Detlef,
danke für die Antwort - aber es ist NICHT der Code, welcher mir in irgendeiner Weise Probleme macht.
Es kann nur der Code sein, der den Realitäten auf dem entsprechenden Rechner nicht gewachsen ist.
Eigentlich will ich mich ja "nur" alle 15 Sekunden bei dem Applikationsserver melden [...]
Diese Garantie ist für Windows-Timer einfach nicht möglich. Du könntest versuchsweise GCSettings.LatencyMode auf niedrige Latenz setzen und beobachten, ob das was bringt. Es würde mich wundern, wenn's täte. Ich vermute eher dass Du dafür sorgen musst, dass die ResetTimer()-Methode völlig "reentrant" ist oder eben synchronisiert, so dass sich die ThreadPool-Threads nicht in die Quere kommen.
Gruß
Marcel -
Hallo Marcel,
es gibt in der Anwendung keinen Threadpool. Es gibt - wenn ich mich recht entsinne - genau 3 selbst programmierte Threads. Jeder dieser Threads ist einzigartig und jeder wird nur ein einziges Mal beim Start der Anwendung erzeugt und gestartet. Jeder dieser Threads hat eine komplett andere Aufgabe und jeder läuft "unendlich".
Bei 2 der 3 Threads (eben auch bei dem, um den es hier geht)ist es so, dass wenn er auf einen Fehler läuft, die Anwendung beendet werden muss. Bei diesem ist es so, dass der Benutzer automatisch vom Applikationsserver abgemeldet wird, wenn der Client sich nicht alle 30 Sekunden meldet. Der Benutzer kann sich dann nur erneut anmelden - muss also die Anwendung neu starten. Es mag ja sein, dass ich in keinem Fall wirklich die Garantie bekomme, dass ein Timer auf die Millisekunde genau auslöst. Aber auf 15 Sekunden genau sollte schon drin sein. Egal, was auf dem System los ist. Abgesehen davon tritt das wohl auch auf Systemen auf, auf denen (scheinbar) gar nichts los ist. Ich hatte zunächst Win7 in Verdacht, da sowohl mein Rechner als auch die Rechner einiger Kollegen sich manchmal seltsame Auszeiten genehmigen. In dieser Zeit kann ich zwar die Maus bewegen - aber der Rest schein wie eingefroren. Nach etlichen Sekunden (gefühlt 1 - 2 Minuten) geht es dann plötzlich weiter. Aber daran kann es nicht liegen, da in der Zeit wo das Problem auftrat keine anderen Auffälligkeiten bemerkt wurden. Es liegt wohl definitiv an dem Client, der sich seltsam verhält. BTW hatten wir das früher anders herum: Der Server hat den Client "angepingt" und wenn der nicht mehr antwortete, erfolge die Abmeldung. Aber als wir das auf .NET umgestellt hatten, war es plötzlich auch so, dass der Client - obwohl noch laufend - nicht antwortete. Bis dahin hatten wir zumindest ein so seltsames Verhalten noch nicht.Gruß
Detlef
-
Hallo Detlef,
Es gibt in der Anwendung keinen Threadpool.
Doch: Die Callback-Methode die System.Threading.Timer aufruft wird - wie bereits erwähnt - immer auf einem gerade freien Threadpoolthread aufgerufen (s. Dokumentation).Alle 15 Sekunden also wird im Normalfall die Methode ResetTimer() auf einem anderen Thread ausgeführt als der auf dem der Timer instanziert wurde. Du kannst das sehr einfach überprüfen, indem Du in der Konsole System.Threading.Thread.CurrentThread.ManagedThreadId ausgibst.
Das unterschiedliche Verhalten hat auch mit den einzelnen Rechnern zu tun, schließlich haben diese verschiedene Prozessoren mit einer unterschiedlichen Anzahl Kerne und mit unterschiedlichen Belastungen. Aber das Hauptproblem bleibt der Code, der so nicht zuverlässig funktionieren kann.
Gruß
Marcel- Als Antwort markiert Detlef Ernst Mittwoch, 2. Oktober 2013 09:57
-
Hallo Detlef,
ich hatte eine ähnliche Anforderung. Bei mir ist es ein Poll Thread, welcher mit dynamischem Interval beim AppServer nachfrägt, ob es aktuell einen Berechnungsfortschritt gibt. Wenn ja, wird die Poll Zeit beschleunigt um einen kontinuierlichen Progress zu bekommen, bis dann kein Progress mehr kommt und die Poll Zeit wird wieder gebremst.
Implementiert hab ich das folgendermaßen mit einem AutoResetEvent für die Intervalzeit:
Kann es bei den Clients sein, dass eventuell eine automatische Wartung losgeht wenn nix daran gemacht wird, bzw. ein Virenscanner startet mit HighPriority? Evtl. bremst das deinen Thread in dieser Form aus.ManualResetEvent _shutdownEvent = new ManualResetEvent(false); AutoResetEvent _intervalEvent = new AutoResetEvent(true); Thread _pollThread = null; public void StartPoll() { _pollThread = new Thread(new ParameterizedThreadStart(this.Poll)); _pollThread.Name = "PollThread"; _pollThread.IsBackground = true; this._pollThread.Start(this); } public void StopPoll() { _shutdownEvent.Set(); _pauseEvent.Set(); if (_pollThread != null) { _pollThread.Abort(); _pollThread = null; } } private void Poll(object obj) { MyProxy proxy = (MyProxy)obj; while (true) { try { if (_shutdownEvent.WaitOne(0, true)) return; proxy.Poll(); _intervalEvent.WaitOne(pollFrequency, true); } catch (ThreadAbortException) { Thread.ResetAbort(); return; } catch (Exception ex) { Logger.LogProgError(ex); return; } } }
Gruß
Stefan