Entwickler-Ecke
WinForms - Korrektes Beenden eines Timers
Määx - Mo 10.02.14 21:11
Titel: Korrektes Beenden eines Timers
Hallo zusammen,
ich habe diverse Forms bzw UserControls bei denen ich System.Timers.Timer nutze. Diese starte ich it myTimer.Start() spätestens in meinem Destruktor rufe ich die myTimer.Stop() auf.
C#-Quelltext
1: 2: 3: 4: 5:
| ~MyClass() { myTimer.Stop(); myTimer.Dispose(); } |
Setze ich nun einen Breakpoint auf den Konstukrot wird dieser nie aufgerufen. Nun habe ich es manchmal, dass beim schliessen der Form der Prozess nicht richtig beendet wird und vermute, dass es daran liegt, dass der Timer nicht richtig beendet wird.
Was mache ich da falsch? Warum wird der destruktor nicht aufgerufen? Hat da jemand eine Idee?
Vielen Dank
Määx
Ralf Jansen - Mo 10.02.14 21:41
Das ist kein Destruktor es ist ein Finalizer und der wird aufgerufen wenn der Garbage Collector meint das es Zeit ist den aufzurufen unter Umständen, wenn das System meint es ist unnötig aufzuräumen, passiert das nie bzw. erst beim sauberen Beenden des Prozesses.
Am Timer hängen Systemresourcen die sollten explizit zum richtigen (frühestmöglichen) Zeitpunkt freigegeben werden darum implementiert der Timer ja auch IDisposable damit man da explizit diese Resourcen freigeben kann. Vermutlich sollte deine MyClass auch IDisposable implementieren und du in der Dispose Methode von MyClass Dispose vom Timer aufrufen. Als letzte ~Verteidigungslinie~ kannst du dann im Finalizer die eigene Dispose Methode aufrufen falls du im Code selbst vergessen hast Dispose aufzurufen. Für das typische Dispose Pattern siehe die
Msdn [
http://msdn.microsoft.com/de-de/library/fs2xkftw(v=vs.110).aspx].
Stop des Timers stoppt das feuern neuer Elapsed Events beendet aber nicht eventuell noch laufende Elapsed Threads. Vielleicht musst du noch das Ende der Threads abwarten weil du irgendwas lang laufendes in dem Event veranstaltest.
Määx - Mo 10.02.14 21:54
Hey,
danke für die schnelle Antwort. Verstehe jetzt zwar das Prinzip, bin mir jedoch nicht ishcer wie ich es umsetzen kann. Das IDisposable hatte ich auch gefunden und probiert. Leider geht es teilweise aber eben um ein UserControl, welches ja selbst IDisposable implementiert (im durch den Designer erstellten Code) und somit kann ich das ja nicht selbst ein weiteres mal überschreiben?
Vielen Dank
Määx
Edit: Habe die Dispose(bool) jetzt einfach aus dem Designer-Code ausgeschnitten und bei mir eingefügt - das klappt zwar, kommt mir aber irgendwie falsch vor?
Ralf Jansen - Mo 10.02.14 22:09
Im Designer Code gibts Dispose ? Da werden maximal Events mit Methoden verknüpft falls ein Control ein Disposed Event hat. Aber die Dispose Implementierung selbst liegt im Framework die kann man nicht einfach hin und herkopieren.
Edit: Ok es wird eine Dispose Methode generiert. Warum auch immer war mir nicht bewußt. Controls haben ein explizites Disposed Event das man verdrahten kann. Den generierten Code solltest du in Ruhe lassen.
Määx - Mo 10.02.14 22:35
oh verdammt - das hatte ich erst gesucht, aber im Designer zeigt mir das VisualStudio das Event nicht an - mit this.Disposed kann ich aber eins hinzufügen :)
Super, Danke! Dann ist es ja recht "einfach" :)
Määx - Do 20.02.14 11:47
ok, irgendwie bekomme ich das nicht hin :(
Ich habe insgesamt folgendes Design: Aus meiner MainApp starte ich mehrere Fenster, denen ich ein "globales" Objekt Manager mitgebe, auf dessen events sie reagieren können. Eines der Fenster startet dann eben einen Timer. Aussehen sieht das stark reduziert dann wie folgt:
C#-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24:
| public partial class MainApp : Form { private Manager _manager = null; private WindowA _windowA = null;
public MainApp() { manager = new Manager(); OpenWindowA(); } public void OpenWindowA() { if(_windowA==null) { _windowA = new WindowA(_manager); _windowA.Disposed += _windowA_disposed; } } private void _windowA_disposed(object sender, EventArgs e) { _windowA = null; } } |
Mein WindowA besteht aus einer Form, einem Form-Interface und einem Presenter:
C#-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34:
| public partial class WindowA: Form, IWindowA { private WindowAPresenter _presenter = null; private System.Timers.Timer _timer;
public WindowA(Manager manager) { _presenter = new WindowAPresenter(this, manager); this.Disposed += WindowA_Disposed; _timer = new System.Timers.Timer(1000); _timer.Elapsed += timer_Elapsed; } void WindowA_Disposed(object sender, EventArgs e) { _presenter.Stop(); _timer.Elapsed -= timer_Elapsed; _timer.Stop(); _timer.Dispose(); } private void timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) { if (textBox.InvokeRequired) textBox.Invoke(new Action(() => { textBox.Text = "Test"; })); else textBox.Text = "Test"; } internal void SetText(string value) { if (textBoxPresenter.InvokeRequired) textBoxPresenter.Invoke(new Action(() => { textBoxPresenter.Text = value; })); else textBoxPresenter.Text = value; } } |
C#-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40:
| public partial class WindowAPresenter { private Manager _manager = null; private IWindowA _view = null; private System.Timers.Timer _timer;
public WindowAPresenter(IWindowA view, Manager manager) { _manager = manager; _view = view; _timer = new System.Timers.Timer(1000); _timer.Elapsed += timer_Elapsed; _manager.Started += _manager_Started; _manager.Stoped += _manager_Stoped; } ~WindowAPresenter() { _timer.Dispose(); } internal void Stop() { _manager.Started -= _manager_Started; _manager.Stoped -= _manager_Stoped; _timer.Elapsed -= timer_Elapsed; _timer.Stop(); } private void timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) { _view.SetText("test"); } private void _manager_Started(object sender, EventArgs e) { _Timer.Start(); } private void _manager_Stoped(object sender, EventArgs e) { _Timer.Stop(); } } |
Jetzt bekomme ich -manchmal- folgende Fehlermeldungen. Zum einen in der WindowA.SetText - Methode die aussage, dass auf das verworfene TextFeld nicht mehr zugegriffen werden kann (Hier scheint also noch der Timer weiter zulaufe, obwohl ich alles beim FormClosing/Disposed stoppe. Und zum anderen erhalte ich im WindowsA-Designer-Code in der überschirebenen Dispose-Methode an der stelle base.Dispose folgende Fehlermeldung:
Quelltext
1:
| Es wurde versucht, einen RCW freizugeben, der derzeit benutzt wird. Der RCW wird im aktiven Thread oder einem anderen Thread verwendet. Der Versuch, einen RCW freizugeben, der gerade verwendet wird, kann Datenbeschädigung oder -verlust zur Folge haben. |
Ich hatte auch versucht in die TimerElapsed-Funktionen jeweil am Anfang eine bool-Variable timerIsActive auf true zu setzen und am Ende auf false und in der WindowA.dispose/formClosing bzw WindowAPresenter.Stop jeweil eine while(timerIsActive); vor dem timer.Dispose einzuubauen. Dabei habe ich dann aber endlosschleifen generiert...
Ich habe jetzt echt keine Idee mehr, wie ich die Timer nun korrekt nutzen muss? Kann mir jemand erklären wo mein Fehler liegt?
Vielen vielen Dank
Määx
Edit: gut das mit Endlosschleife ist ja logisch, da ich im TimerElapsed ja den GUI-Thread joinen muss. War ne dumme Idee :)
Th69 - Do 20.02.14 12:35
Hallo Määx,
warum benutzt du nicht den System.Windows.Forms.Timer? Dann bräuchtest du nicht den Zugriff auf UI-Elemente mittels Control.Invoke durchführen und hättest auch wahrscheinlich mit dem Stoppen und Disposen weniger Probleme.
Der System.Timers.Timer ist ja hauptsächlich für Dienste oder aber Hintergrundprozesse gedacht.
Määx - Do 20.02.14 14:28
mh, dachte immer, man soll den nicht verwenden weil er den GUI-Thread verlangsamt. Aber ich probier es mal aus. Muss ich dann auch dispose usw. aufrufen oder soll ich das dann ignorieren?
Edit: Wunderbar! Habe den Timer ersetzt und keine Probleme mehr! Klasse! Aber aus interesse würde ich trotzdem ganz gerne verstehen wie ich es richtig lösen müsste?
Th69 - Do 20.02.14 14:39
Hallo,
natürlich kommt es drauf an, was du in der Timer-Routine machen willst, d.h. wie lange ein Tick dauert (der UI-Thread sollte nicht mehr als 10-50ms unterbrochen werden).
Bisher delegierst du ja den gesamten Timer.Tick (bzw. Elapsed) zurück in den UI-Thread, d.h. du gewinnst nichts dadurch.
Als Alternative würde ich dann aber eher den System.Threading.Timer verwenden.
Ich selber hatte bisher noch keine Probleme, den Timer zu stoppen (und den Rest vom GC erledigen zu lassen).
Ralf Jansen - Do 20.02.14 14:58
Zitat: |
Als Alternative würde ich dann aber eher den System.Threading.Timer verwenden. |
Der zuerst benutzte Timer kapselt den Threading.Timer (ich glaube hauptsächlich damit man einen Event hat und nicht diesen doofen Callback Syntax). Das verhalten sollte also eigentlich identisch sein und dann genauso knallen wenn man einen synchronisierten Zugriff auf die UI macht ohne zu prüfen ob die Form disposed ist.
Wenn das einzige was Elapsed tut updaten der Form ist dann kann man auch gleich den ganzen Event im Hauptthread ausführen lassen. Dazu einfach die SynchronizingObject Property des Timers (System.Timers.Timer um genau zu sein) auf die Form setzen. Darin zur Sicherheit prüfen ob den die Form disposed ist.
C#-Quelltext
1: 2: 3: 4: 5: 6: 7: 8:
| _timer.SynchronizingObject = this;
private void timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) { if(!textBox.IsDisposed) textBox.Text = "Test"; } |
Oder eben einfach den auf WM_Timer beruhenden Winforms Timer nehmen. Ohne Threads kein Synchronisierungsproblem.
Entwickler-Ecke.de based on phpBB
Copyright 2002 - 2011 by Tino Teuber, Copyright 2011 - 2025 by Christian Stelzmann Alle Rechte vorbehalten.
Alle Beiträge stammen von dritten Personen und dürfen geltendes Recht nicht verletzen.
Entwickler-Ecke und die zugehörigen Webseiten distanzieren sich ausdrücklich von Fremdinhalten jeglicher Art!