Christian H. Kuhn
2016-08-24 14:36:42 UTC
Hallo Gemeinde,
Immer noch die Schachuhr.
Projekt: https://www.qno.de/gitweb/?p=qchessclock.git;a=summary
Snapshot unter
https://www.qno.de/gitweb/?p=qchessclock.git;a=snapshot;h=9ad77df87ad2dc11c8fa72816bec8c5e5459ea77;sf=tgz
Alles lief, und da musste ich doch was ändern :-)
Ich glaubte, die Multithreading-Probleme gelöst zu haben. Um die
Systemlast ein klein wenig zu reduzieren, bin ich auf die Idee gekommen,
in der QChessClock in notifyObservers() auf changed zu prüfen. changed
wird gesetzt, wenn von außen ein Knopf gedrückt oder die Bedenkzeit
verändert wird. Bei jedem Aufruf von notifyObservers wird außerdem
überprüft, ob sich die anzuzeigende (also auf Sekunden gerundete)
Bedenkzeit geändert hat oder ob ein Blättchen gefallen ist. Auch dann
wird changed gesetzt. Nur wenn sich etwas geändert hat, wird das Objekt
zum Übertragen des Status gefüllt, und nur dann werden die Observer mit
diesem Objekt benachrichtigt.
Dies führt wie erhofft dazu, dass Observer nicht mehr alle 20 ms,
sondern nur bei einer Benutzeraktion bzw. beim Umspringen der
Sekundenanzeige benachrichtigt werden. In der Swing-GUI funktioniert
auch weiterhin alles.
Schwierigkeiten machen die JUnit-Tests. Die Testklasse implementiert die
Funktionen einer GUI, um die zu testende Klasse anzusprechen. Die
Testklasse registriert sich als Observer. Bislang wurde alle 20 ms die
Update-Funktion von notifyObservers() aufgerufen:
@Override
public final void update(final QChessClockSTO _sto) {
synchronized (this) {
state = _sto.getStatus();
(...) // weitere übertragene Daten
notified = true;
notifyAll();
}
}
Im Test wurde eine Aktion auf der zu testenden Klasse aufgerufen, mit
waitForNotify() auf den eintreffenden update() gewartet und dann die
entsprechenden asserts vorgenommen. Ob ein update() verpasst wurde oder
nicht, war nicht wichtig, der nächste kam ja 20 ms später.
private void waitForNotify() throws InterruptedException {
synchronized (this) {
notified = false;
while (!notified) {
wait();
}
}
}
Durch den Test auf changed erfolgt jetzt nur noch ein einziger update()
als Reaktion auf die Benutzeraktion. Wird der verpasst, kommt kein
weiterer mehr, und waitForNotify hängt in der Endlosschleife. Also
folgende Hilfsfunktion:
private void pressButton(final Buttons _button) throws
InterruptedException {
synchronized (this) {
notified = false;
switch (_button) {
case LEFT_:
sut.moveButtonPressed(LEFT);
break;
(...) // weitere Knöpfe
default:
sut.resetPressed();
break;
}
while (!notified) {
wait();
}
}
}
Im Test wird pressButton() aufgerufen, danach die Asserts durchgeführt.
Ich glaubte, folgendes erreicht zu haben: pressButton holt sich den
Lock, löscht notified und führt die Benutzeraction an der Uhr aus.
Danach wird die Endlosschleife betreten, der Lock freigegeben und auf
notifyAll() gewartet. Irgendwann kommt der update(). Der braucht den
Lock. Kommt der update, bevor wait() erreicht ist, ist der Lock noch
belegt, und update() muss warten. Bekommt update() den Lock, werden die
Daten aktualisiert, notified wird gesetzt, notifyAll() aufgerufen und
der Lock freigegeben. wait() erkennt den notifyAll(), beendet das
Warten, bekommt den Lock zurück. Die Endlosschleife wird beendet, die
Daten sind aktuell und können mit Assert überprüft werden.
Auf QChessClock benutzen die Benutzer-Zugriffsmethoden und
notifyObservers den gleichen Lock. Wenn ein Knopf gedrückt wird, kann
der Lock für die angeforderte Methode nur erhalten werden, wenn
notifyObservers gerade nicht läuft. Während diese Methode ausgeführt
wird, an deren Schluss setChanged(true) aufgerufen wird, kann
notifyObservers nicht ausgeführt werden. Erst wenn die
Benutzer-Zugriffsmethode beendet ist, erfolgt der nächste
notifyObservers. Der sieht changed = true und ruft auf der Testklasse
update() auf.
Soweit die Theorie. Beim Ausführen der Tests kommt es unregelmäßig zu
Fehlern. Tests bleiben einfach mal stehen, weil notified false bleibt.
Da wird ein update() verschluckt. Im Debugger funktioniert alles immer
bestens. Da ist das update schon am Warten, wenn das wait() erreicht
ist, sobald wait() ausgeführt ist, ist notified true, und alles ist gut.
Im echten Ablauf passiert das aus mir unbekannten Gründen anscheinend nicht.
Diesen Fehler konnte ich nicht beobachten, wenn ich eine Testmethode
einzeln ausführe. Führe ich die Testklasse aus, egal ob in Eclipse oder
mit Gradle, tritt der Fehler an einer anscheinend zufälligen Testmethode
auf.
Ich halte das für ein deutliches Zeichen, dass da was mit der
Nebenläufigkeit nicht stimmt. Ich finde den Fehler aber nicht. Wer winkt
mit dem Zaunpfahl?
TIA
QNo
Immer noch die Schachuhr.
Projekt: https://www.qno.de/gitweb/?p=qchessclock.git;a=summary
Snapshot unter
https://www.qno.de/gitweb/?p=qchessclock.git;a=snapshot;h=9ad77df87ad2dc11c8fa72816bec8c5e5459ea77;sf=tgz
Alles lief, und da musste ich doch was ändern :-)
Ich glaubte, die Multithreading-Probleme gelöst zu haben. Um die
Systemlast ein klein wenig zu reduzieren, bin ich auf die Idee gekommen,
in der QChessClock in notifyObservers() auf changed zu prüfen. changed
wird gesetzt, wenn von außen ein Knopf gedrückt oder die Bedenkzeit
verändert wird. Bei jedem Aufruf von notifyObservers wird außerdem
überprüft, ob sich die anzuzeigende (also auf Sekunden gerundete)
Bedenkzeit geändert hat oder ob ein Blättchen gefallen ist. Auch dann
wird changed gesetzt. Nur wenn sich etwas geändert hat, wird das Objekt
zum Übertragen des Status gefüllt, und nur dann werden die Observer mit
diesem Objekt benachrichtigt.
Dies führt wie erhofft dazu, dass Observer nicht mehr alle 20 ms,
sondern nur bei einer Benutzeraktion bzw. beim Umspringen der
Sekundenanzeige benachrichtigt werden. In der Swing-GUI funktioniert
auch weiterhin alles.
Schwierigkeiten machen die JUnit-Tests. Die Testklasse implementiert die
Funktionen einer GUI, um die zu testende Klasse anzusprechen. Die
Testklasse registriert sich als Observer. Bislang wurde alle 20 ms die
Update-Funktion von notifyObservers() aufgerufen:
@Override
public final void update(final QChessClockSTO _sto) {
synchronized (this) {
state = _sto.getStatus();
(...) // weitere übertragene Daten
notified = true;
notifyAll();
}
}
Im Test wurde eine Aktion auf der zu testenden Klasse aufgerufen, mit
waitForNotify() auf den eintreffenden update() gewartet und dann die
entsprechenden asserts vorgenommen. Ob ein update() verpasst wurde oder
nicht, war nicht wichtig, der nächste kam ja 20 ms später.
private void waitForNotify() throws InterruptedException {
synchronized (this) {
notified = false;
while (!notified) {
wait();
}
}
}
Durch den Test auf changed erfolgt jetzt nur noch ein einziger update()
als Reaktion auf die Benutzeraktion. Wird der verpasst, kommt kein
weiterer mehr, und waitForNotify hängt in der Endlosschleife. Also
folgende Hilfsfunktion:
private void pressButton(final Buttons _button) throws
InterruptedException {
synchronized (this) {
notified = false;
switch (_button) {
case LEFT_:
sut.moveButtonPressed(LEFT);
break;
(...) // weitere Knöpfe
default:
sut.resetPressed();
break;
}
while (!notified) {
wait();
}
}
}
Im Test wird pressButton() aufgerufen, danach die Asserts durchgeführt.
Ich glaubte, folgendes erreicht zu haben: pressButton holt sich den
Lock, löscht notified und führt die Benutzeraction an der Uhr aus.
Danach wird die Endlosschleife betreten, der Lock freigegeben und auf
notifyAll() gewartet. Irgendwann kommt der update(). Der braucht den
Lock. Kommt der update, bevor wait() erreicht ist, ist der Lock noch
belegt, und update() muss warten. Bekommt update() den Lock, werden die
Daten aktualisiert, notified wird gesetzt, notifyAll() aufgerufen und
der Lock freigegeben. wait() erkennt den notifyAll(), beendet das
Warten, bekommt den Lock zurück. Die Endlosschleife wird beendet, die
Daten sind aktuell und können mit Assert überprüft werden.
Auf QChessClock benutzen die Benutzer-Zugriffsmethoden und
notifyObservers den gleichen Lock. Wenn ein Knopf gedrückt wird, kann
der Lock für die angeforderte Methode nur erhalten werden, wenn
notifyObservers gerade nicht läuft. Während diese Methode ausgeführt
wird, an deren Schluss setChanged(true) aufgerufen wird, kann
notifyObservers nicht ausgeführt werden. Erst wenn die
Benutzer-Zugriffsmethode beendet ist, erfolgt der nächste
notifyObservers. Der sieht changed = true und ruft auf der Testklasse
update() auf.
Soweit die Theorie. Beim Ausführen der Tests kommt es unregelmäßig zu
Fehlern. Tests bleiben einfach mal stehen, weil notified false bleibt.
Da wird ein update() verschluckt. Im Debugger funktioniert alles immer
bestens. Da ist das update schon am Warten, wenn das wait() erreicht
ist, sobald wait() ausgeführt ist, ist notified true, und alles ist gut.
Im echten Ablauf passiert das aus mir unbekannten Gründen anscheinend nicht.
Diesen Fehler konnte ich nicht beobachten, wenn ich eine Testmethode
einzeln ausführe. Führe ich die Testklasse aus, egal ob in Eclipse oder
mit Gradle, tritt der Fehler an einer anscheinend zufälligen Testmethode
auf.
Ich halte das für ein deutliches Zeichen, dass da was mit der
Nebenläufigkeit nicht stimmt. Ich finde den Fehler aber nicht. Wer winkt
mit dem Zaunpfahl?
TIA
QNo