Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]
Groups > de.comp.lang.java > #12987 > unrolled thread
| Started by | "Christian H. Kuhn" <qno-news@qno.de> |
|---|---|
| First post | 2016-07-14 01:09 +0200 |
| Last post | 2016-07-17 16:02 +0200 |
| Articles | 20 on this page of 29 — 3 participants |
Back to article view | Back to de.comp.lang.java
JUnit Test von JButton: Action wird nicht erkannt "Christian H. Kuhn" <qno-news@qno.de> - 2016-07-14 01:09 +0200
Re: JUnit Test von JButton: Action wird nicht erkannt "Christian H. Kuhn" <qno-news@qno.de> - 2016-07-14 18:26 +0200
Re: JUnit Test von JButton: Action wird nicht erkannt Patrick Roemer <sangamon@netcologne.de> - 2016-07-14 19:47 +0200
Re: JUnit Test von JButton: Action wird nicht erkannt "Christian H. Kuhn" <qno-news@qno.de> - 2016-07-15 00:08 +0200
Re: JUnit Test von JButton: Action wird nicht erkannt Patrick Roemer <sangamon@netcologne.de> - 2016-07-15 22:04 +0200
Re: JUnit Test von JButton: Action wird nicht erkannt "Christian H. Kuhn" <qno-news@qno.de> - 2016-07-15 22:53 +0200
Re: JUnit Test von JButton: Action wird nicht erkannt "Christian H. Kuhn" <qno-news@qno.de> - 2016-07-15 23:09 +0200
Re: JUnit Test von JButton: Action wird nicht erkannt Patrick Roemer <sangamon@netcologne.de> - 2016-07-15 23:44 +0200
Countdown Timer Design (was: JUnit Test von JButton: Action wird nicht erkannt) "Christian H. Kuhn" <qno-news@qno.de> - 2016-07-16 23:44 +0200
Re: Countdown Timer Design "Christian H. Kuhn" <qno-news@qno.de> - 2016-07-17 12:44 +0200
Re: Countdown Timer Design "Christian H. Kuhn" <qno-news@qno.de> - 2016-07-17 15:49 +0200
Re: Countdown Timer Design "Christian H. Kuhn" <qno-news@qno.de> - 2016-07-17 17:03 +0200
Re: Countdown Timer Design "Christian H. Kuhn" <qno-news@qno.de> - 2016-07-19 15:59 +0200
Re: JUnit Test von JButton: Action wird nicht erkannt "Christian H. Kuhn" <qno-news@qno.de> - 2016-07-19 14:59 +0200
Re: JUnit Test von JButton: Action wird nicht erkannt "Christian H. Kuhn" <qno-news@qno.de> - 2016-07-19 16:06 +0200
Re: JUnit Test von JButton: Action wird nicht erkannt Patrick Roemer <sangamon@netcologne.de> - 2016-07-19 18:59 +0200
Re: JUnit Test von JButton: Action wird nicht erkannt "Christian H. Kuhn" <qno-news@qno.de> - 2016-07-19 22:35 +0200
Re: JUnit Test von JButton: Action wird nicht erkannt Patrick Roemer <sangamon@netcologne.de> - 2016-07-20 13:00 +0200
Re: JUnit Test von JButton: Action wird nicht erkannt "Christian H. Kuhn" <qno-news@qno.de> - 2016-07-23 20:36 +0200
Re: JUnit Test von JButton: Action wird nicht erkannt "Christian H. Kuhn" <qno-news@qno.de> - 2016-07-23 23:15 +0200
Re: JUnit Test von JButton: Action wird nicht erkannt Wanja Gayk <brixomatic@yahoo.com> - 2016-07-19 23:02 +0200
Re: JUnit Test von JButton: Action wird nicht erkannt "Christian H. Kuhn" <qno-news@qno.de> - 2016-07-20 12:33 +0200
GUI-Update über Swing-EDT (was: JUnit Test von JButton: Action wird nicht erkannt) "Christian H. Kuhn" <qno-news@qno.de> - 2016-07-15 12:03 +0200
Re: GUI-Update über Swing-EDT Patrick Roemer <sangamon@netcologne.de> - 2016-07-15 22:43 +0200
Re: GUI-Update über Swing-EDT "Christian H. Kuhn" <qno-news@qno.de> - 2016-07-15 23:18 +0200
Re: GUI-Update über Swing-EDT "Christian H. Kuhn" <qno-news@qno.de> - 2016-07-16 15:24 +0200
Re: GUI-Update über Swing-EDT Patrick Roemer <sangamon@netcologne.de> - 2016-07-16 16:42 +0200
Re: GUI-Update über Swing-EDT "Christian H. Kuhn" <qno-news@qno.de> - 2016-07-16 23:05 +0200
Re: GUI-Update über Swing-EDT "Christian H. Kuhn" <qno-news@qno.de> - 2016-07-17 16:02 +0200
Page 1 of 2 [1] 2 Next page →
| From | "Christian H. Kuhn" <qno-news@qno.de> |
|---|---|
| Date | 2016-07-14 01:09 +0200 |
| Subject | JUnit Test von JButton: Action wird nicht erkannt |
| Message-ID | <duo028Fb4qaU1@mid.individual.net> |
Hallo Gemeinde,
Ich verzweifle gerade.
Vollständiger Code auf https://www.qno.de/gitweb/, Branch v0.8.4. Die
wichtigsten Ausschnitte:
public class QChessClockJavaAV extends JFrame implements
QChessClockObserver, ActionListener {
(...)
private final transient JButton resetButton;
(...)
public QChessClockJavaAV(final QChessClock _clock) {
super("QChessClock – The JAVA Chess Clock from your FIDE Arbiter");
final JPanel contentPanel = new JPanel();
contentPanel.setLayout(new GridBagLayout());
final GridBagConstraints constraint = new GridBagConstraints();
constraint.fill = GridBagConstraints.BOTH;
constraint.weightx = 1;
(...)
resetButton = new JButton("R");
resetButton.setActionCommand("reset");
resetButton.addActionListener(this);
resetButton.setMinimumSize(QChessClockJavaAV.BUTTON_DIMENSION);
resetButton.setName("ResetButton");
constraint.gridx = 1;
constraint.gridy = 1;
contentPanel.add(resetButton, constraint);
}
(...)
public final void actionPerformed(final ActionEvent _event) {
final String myActionCommand = _event.getActionCommand();
switch (myActionCommand) {
case "reset":
clock.resetPressed();
break;
(...)
default:
}
}
(...)
}
public class QChessClockJavaAVTest implements QChessClockObserver {
private transient QChessClockJavaAV sut;
private transient QChessClock clock;
private transient JButton resetButton;
private transient QChessClockState status;
@Before
public final void setUp() throws Exception {
clock = new QChessClock();
clock.addObserver(this);
sut = new QChessClockJavaAV(clock);
resetButton = (JButton) TestUtils.getChildNamed(sut, "ResetButton");
}
public final void update(final QChessClockState _state) {
status = _state;
}
@Test
public final void pressResetButtonTest() throws Throwable {
leftButton.doClick();
stopButton.doClick();
Thread.sleep(TWO_CYCLES);
resetButton.doClick();
Thread.sleep(TWO_CYCLES);
assertSame(QChessClockState.NOT_STARTED, status);
}
}
TestUtils von
http://www.javaworld.com/article/2073056/swing-gui-programming/automate-gui-tests-for-swing-applications.html
Der Test fails. status ist STOPPED, was es nach stopButton.doClick sein
sollte, aber nicht mehr nach resetButton.doClick.
resetButton wurde korrekt aufgefunden, wie durch einen Test mit
setBackground(Color) herausgefunden wurde.
Wird QChessClockJavaAV normal gestartet und mit der Maus bedient,
funktioniert der Button wie geplant.
Alle anderen Knöpfe reagieren wie gewollt auf doClick().
resetButton.doClick() löst keine Aktion aus, oder zumindest wird sie
nicht von actionPerformed() in QChessClockJavaAV aufgefangen. Ein
Breakpoint zu Beginn von actionPerformed() hält im Debugger bei allen
anderen Knöpfen an, wird aber bei resetButton.onClick() nicht getroffen.
Stattdessen wird die nächste Zeile im Test, Thread.sleep(), ausgeführt.
Ich habe auch versucht, den Knopf mit doClick(1000) länger zu drücken.
Gleicher Fehler.
Mir fehlen die Ideen, wo ich noch suchen soll. Alle mir vorstellbaren
Fehlermöglichkeiten sind ausgeschlossen. Und am Ende ist es dann wie
üblich irgendwas ganz primitives ...
TIA
QNo
[toc] | [next] | [standalone]
| From | "Christian H. Kuhn" <qno-news@qno.de> |
|---|---|
| Date | 2016-07-14 18:26 +0200 |
| Message-ID | <dupsqgFp05jU1@mid.individual.net> |
| In reply to | #12987 |
Am 14.07.2016 um 01:09 schrieb Christian „Ingrid“ Kuhn: Die Tests waren natürlich extrem umständlich. Also aus QChessClock ein Interface extrahiert. Die Testklasse implementiert jetet dieses Interface und schleicht sich als Mock-Uhr in die zu testende GUI ein. Damit können die Methodenaufrufe wesentlich leichter getestet werden. Der Reset-Button reagiert allerding immer noch nicht. Weiteres Herumspielen mit Farbmarkierungen funktionierte bei diesem Knopf auch nicht. Allerdings kam dadurch der entscheidende Hinweis: Der Button war schlicht und ergreifend nicht aktiviert. Nach setEnabled(true) ist dann alles gelaufen. lg QNo
[toc] | [prev] | [next] | [standalone]
| From | Patrick Roemer <sangamon@netcologne.de> |
|---|---|
| Date | 2016-07-14 19:47 +0200 |
| Message-ID | <nm8j8b$r9c$1@newsreader4.netcologne.de> |
| In reply to | #12987 |
Responding to Christian H. Kuhn:
> Vollständiger Code auf https://www.qno.de/gitweb/, Branch v0.8.4.
Vielleicht bin ich nur zu doof, aber ich sehe in diesem Interface keinen
Weg, das ganze Projekt einfach auszuchecken. Falls es das nicht gibt,
wäre ein Link auf ein Zip oder, noch besser, ein clone des Repos auf
github o.ä., sinnvoller.
Was auf den ersten Blick auffällt: "Echtzeit" (System.nanoTime(),
java.util.Timer, etc.) in den Kernklassen hart zu verdrahten, ist schon
wegen der Tests nicht so der Knüller - die brauchen ja jetzt schon ewig...
Ich würde das in irgendeine TimeSource-Abstraktion auslagern, die man in
den Tests mit gescripteten Zeitwerten/-events mocken kann. Das mag die
Sache durchaus etwas komplexer machen, ist aber IMHO "essential
complexity" - Zeit ist nun mal nicht einfach. :) Ich würde erwarten,
dass eine solche Umstellung das Design - unabhängig von den Tests -
letztendlich verbessert, weil die Abhängigkeiten vom Zeitverlauf dadurch
klarer ersichtlich werden sollten und man Threading (s.u.) besser
wegkapseln kann.
> public class QChessClockJavaAV extends JFrame implements
> QChessClockObserver, ActionListener {
Statt die komplette Eventlogik in einem einzigen ActionListener zu
dispatchen, würde ich jedem Widget (bzw. deren Models) einen eigenen
(ggfs. anonymen) ActionListener spendieren, bevorzugt in Form einer
javax.swing.Action.
> resetButton.doClick() löst keine Aktion aus, oder zumindest wird sie
> nicht von actionPerformed() in QChessClockJavaAV aufgefangen.
Dass das an resetButton.isEnabled() == false liegt, hast Du ja schon
selber herausgefunden. Sowas ist übrigens genau die Art von Logik, die
man in einer State Machine in einem Presentation Model kapseln und
unabhängig vom UI-Framework testen kann. :) Die Widgets (bzw. deren
Models) würden dann einfach mit den Events aus dem PM verdrahtet.
Ansonsten hast Du auf jeden Fall noch ein Threading-Problem. Sowohl der
Test (main-Thread) als auch das Kernmodell (Timer-Threads) werkeln
direkt auf dem UI-Zustand rum. Das muss alles über den Swing-EDT laufen.
Das ist kein rein akademisches Problem! Wenn ich im Debugger etwas mit
Breakpoints und Timings rumkaspere, schaffe ich es z.B., diesen Testfall
(natürlich nichtdeterministisch und nur selten) grün laufen zu lassen -
weil das Deaktivieren des Reset-Buttons noch nicht im EDT "angekommen"
ist und auch die stop-Action "untergeht". Das sieht dann etwa so aus:
<snip>
new state: NOT_STARTED
thread: main
from: QChessClock.addObserver(QChessClock.java:196)
new state: NOT_STARTED
thread: Timer-2
from: QChessClock$1.run(QChessClock.java:48)
new state: RUNNING
thread: main
from: QChessClock.leftPressed(QChessClock.java:78)
new state: NOT_STARTED
thread: main
from: QChessClock.resetPressed(QChessClock.java:136)
</snip>
Viele Grüße,
Patrick
[toc] | [prev] | [next] | [standalone]
| From | "Christian H. Kuhn" <qno-news@qno.de> |
|---|---|
| Date | 2016-07-15 00:08 +0200 |
| Message-ID | <duqgrdFtso5U1@mid.individual.net> |
| In reply to | #12989 |
Am 14.07.2016 um 19:47 schrieb Patrick Roemer: > Responding to Christian H. Kuhn: >> Vollständiger Code auf https://www.qno.de/gitweb/, Branch v0.8.4. > > Vielleicht bin ich nur zu doof, aber ich sehe in diesem Interface keinen > Weg, das ganze Projekt einfach auszuchecken. Ich hab auch gebraucht ... auschecken geht nicht, stattdessen kann man einen Snapshot runterladen, das ist ein Zip-File. Aufs gewünschte Projekt (oder dahinter auf shortlog) clicken, beim gewünschten Commit auf Snapshot. > wäre ein Link auf ein Zip oder, noch besser, ein clone des Repos auf > github o.ä., sinnvoller. GitHub kommt später. Ich habe versucht, apache und git mit „dummem“ http aufzusetzen. Bislang ohne Erfolg, das schaue ich mir später nochmal an. Wenn das läuft, ist ein einfaches Clonen über http möglich. > Was auf den ersten Blick auffällt: "Echtzeit" (System.nanoTime(), > java.util.Timer, etc.) in den Kernklassen hart zu verdrahten, ist schon > wegen der Tests nicht so der Knüller - die brauchen ja jetzt schon ewig... > > Ich würde das in irgendeine TimeSource-Abstraktion auslagern, die man in > den Tests mit gescripteten Zeitwerten/-events mocken kann. Ich hab ein wenig gebraucht, um zu begreifen, was du von mir willst :-) Ich glaube verstanden zu haben: Wenn ich nicht System.nanoTime() nehme, sondern eine AbstractTime.getTime(), dann kann ich „in echt“ den 50 ms-Takt zum Aktualisieren nehmen. Bei den Tests brauche ich keine Rücksicht auf Zeitabläufe zu nehmen, sondern setze die Zeiten so, wie ich sie brauche? > Presentation Model Einer der nächsten Schritte. Autonomous view klappt jetzt, quick and dirty. Da dort jedes Widget eine eigene Repräsentation seines Zustands hat, bekommt auch jedes seinen eigenen ActionListener. > Ansonsten hast Du auf jeden Fall noch ein Threading-Problem. Sowohl der > Test (main-Thread) als auch das Kernmodell (Timer-Threads) werkeln > direkt auf dem UI-Zustand rum. Das muss alles über den Swing-EDT laufen. > > Das ist kein rein akademisches Problem! Wenn ich im Debugger etwas mit > Breakpoints und Timings rumkaspere, schaffe ich es z.B., diesen Testfall > (natürlich nichtdeterministisch und nur selten) grün laufen zu lassen - > weil das Deaktivieren des Reset-Buttons noch nicht im EDT "angekommen" > ist und auch die stop-Action "untergeht". Das sieht dann etwa so aus: Um mich da rauszuwinden, habe ich in den Tests mit hinreichend langen Thread.sleep() gearbeitet. Und in der Tat gibt es Probleme. Die Wartezeit muss durch Ausprobieren so gewählt werden, dass der Test sowohl bei normaler Ausführung als auch bei der langsameren Coverage-Ausführung (und natürlich auch mit Breakpoint) grün bleibt. Unschön. Im Studium hatte ich Multi-Threading nur im ProPra, und der einzige wirkliche Lerneffekt war, dass der Datenfluss von einem Thread in den anderen EWIG dauert. Threadsicherheit, Transaktionen etc. hab ich mal gehört, im Prinzip verstanden, aber noch keinen praktischen Kontakt gehabt. Schien mir hier auch nicht nötig. In der gegebenen Situation (Schach) werden auch geübte Spieler nicht mehr als 2 Züge pro Sekunde machen. Bei einem 50ms-Takt passiert da nichts gravierendes. notifyObserver() überträgt immer nur die aktuellen Änderungen, und aufeinanderfolgende Aufrufe werden da keine vom Spieler wahrnehmbaren Störeffekte erzeugen. JUnit nimmt da etwas filigraner wahr. Und das überspiele ich zur Zeit noch mit Thread.sleep(). Mal schauen, was ich da wie in den EDT bekomme. Wobei ich denke, dass der TimerTask in QChessTimer unproblematisch ist. Zur Not wird auf flagFallen synchronisiert. Den TimerTask in QChessClock kann man womöglich ganz wegbekommen, wenn QChessTimer in seinem Takt auf Veränderungen in den Sekunden testet und dann QChessClock benachrichtigt. Wie das externe Setzen von GUI-Bestandteilen in den EDT rein soll, erschließt sich mir noch nicht. Im Moment sehe ich eigentlich nur, für ein Update von Eigenschaften entweder ein spezielles (unsichtbares) noch einzufügendes Element der GUI mit doClick() auszulösen oder einem ActionListener ein Event zuzuschicken. Beides wäre auf Swing spezialisiert, und für eine andere Oberfläche müsste ich das Modell umschreiben. Unschön. Ja, inzwischen weiss ich. Presentation Model hat das Problem nicht :-) Ich glaube, der nächste Schritt wird die Umstellung auf Presentation Model. Da unterhält sich nur das PM mit der GUI. lg QNo
[toc] | [prev] | [next] | [standalone]
| From | Patrick Roemer <sangamon@netcologne.de> |
|---|---|
| Date | 2016-07-15 22:04 +0200 |
| Message-ID | <nmbfkk$sk4$1@newsreader4.netcologne.de> |
| In reply to | #12990 |
Responding to Christian H. Kuhn:
> Am 14.07.2016 um 19:47 schrieb Patrick Roemer:
> Ich glaube verstanden zu haben: Wenn ich nicht System.nanoTime() nehme,
> sondern eine AbstractTime.getTime(), dann kann ich „in echt“ den 50
> ms-Takt zum Aktualisieren nehmen. Bei den Tests brauche ich keine
> Rücksicht auf Zeitabläufe zu nehmen, sondern setze die Zeiten so, wie
> ich sie brauche?
Genau. Naiv erst mal:
interface TimeSource {
long getTimeInNanoSecs();
}
class ChessTimer {
public ChessTimer(TimeSource timeSource) {
// ...
}
// ...
}
class SystemTimeSource implements TimeSource {
@override public long getTimeInNanoSecs() {
return System.nanoSecs();
}
}
...und für Tests dann sowas:
class ScriptedTimeSource implements TimeSource {
private long time;
@override public long getTimeInNanoSecs() {
return time;
}
public void set(long time) {
this.time = time;
}
}
...oder ähnlich - kann man je nach Bedarf komplexer gestalten, mit
EasyMock "implementieren", was immer für den konkreten Test am
günstigsten ist.
Das ist aber nur die halbe Miete. Man würde dann auch den konkreten
j.u.Timer in der Kernklasse wegabstrahieren wollen. Dafür könnte man ein
eigenes Interface für die Funktionalität von j.u.Timer erstellen, usw.
Alternativ/zusätzlich könnte man das Design so ändern, dass der
ChessTimer nur eine Methode #checkFlag() hat, die von einem *externen*
Timer aufgerufen wird. Dann braucht man "weiter oben" natürlich wieder
eine Entität, die externen Timer und ChessTimer zusammenführt und muss
die separat testen - aber man hat die Verantwortlichkeiten getrennt und
die "Problemfläche" für Threading reduziert.
> Im Studium hatte ich Multi-Threading nur im ProPra, und der einzige
> wirkliche Lerneffekt war, dass der Datenfluss von einem Thread in den
> anderen EWIG dauert.
Dann war das ProPra für den Fuß. :)
> Threadsicherheit, Transaktionen etc. hab ich mal gehört, im Prinzip
> verstanden, aber noch keinen praktischen Kontakt gehabt. Schien mir hier
> auch nicht nötig. In der gegebenen Situation (Schach) werden auch geübte
> Spieler nicht mehr als 2 Züge pro Sekunde machen. Bei einem 50ms-Takt
> passiert da nichts gravierendes. notifyObserver() überträgt immer nur
> die aktuellen Änderungen, und aufeinanderfolgende Aufrufe werden da
> keine vom Spieler wahrnehmbaren Störeffekte erzeugen.
Es ist völlig egal, welche Frequenz die Aufrufe in den einzelnen Threads
haben. Solange es keine "happens-before"-Beziehung zwischen Aktion A in
Thread 1 und Aktion B in Thread 2 gibt, hast Du keinerlei Garantie, dass
Thread 2 die Resultate von Aktion A je sehen wird, egal, wieviel
Realzeit zwischen den Aktionen liegt.
> Wobei ich denke, dass der TimerTask in QChessTimer unproblematisch ist.
Ist er nicht. #flagFallen müsste mindestens volatile sein, da von
mehreren Threads drauf zugegriffen wird.
> Wie das externe Setzen von GUI-Bestandteilen in den EDT rein soll,
> erschließt sich mir noch nicht.
Über SwingUtilities#invokeLater().
Viele Grüße,
Patrick
[toc] | [prev] | [next] | [standalone]
| From | "Christian H. Kuhn" <qno-news@qno.de> |
|---|---|
| Date | 2016-07-15 22:53 +0200 |
| Message-ID | <dut0r8FhnfgU1@mid.individual.net> |
| In reply to | #12993 |
Am 15.07.2016 um 22:04 schrieb Patrick Roemer: > Dann war das ProPra für den Fuß. :) Naja. Nicht ganz. Das erste größere Projekt, das ich mehr oder weniger alleine bearbeitet habe. Eclipse und Android 2.3.3 halbwegs kennengelernt. Halt Anfängerpraktikum. Die Betreuer waren engagiert, die Aufgabenstellung kreativ und motivierend. Es gibt aber effektiveres Lernen. lg QNo
[toc] | [prev] | [next] | [standalone]
| From | "Christian H. Kuhn" <qno-news@qno.de> |
|---|---|
| Date | 2016-07-15 23:09 +0200 |
| Message-ID | <dut1paFhuo8U1@mid.individual.net> |
| In reply to | #12993 |
Am 15.07.2016 um 22:04 schrieb Patrick Roemer:
> Responding to Christian H. Kuhn:
> interface TimeSource {
> long getTimeInNanoSecs();
> }
> Alternativ/zusätzlich könnte man das Design so ändern, dass der
> ChessTimer nur eine Methode #checkFlag() hat, die von einem *externen*
> Timer aufgerufen wird. Dann braucht man "weiter oben" natürlich wieder
> eine Entität, die externen Timer und ChessTimer zusammenführt und muss
> die separat testen - aber man hat die Verantwortlichkeiten getrennt und
> die "Problemfläche" für Threading reduziert.
Der Gedanke kam mir auch schon. Die Kernfunktionalität von QChessTimer
ist einfach das unterbrechbare Runterzählen und Ausliefern der Zeit. Ob
die Null erreicht ist, kann auch QChessClock prüfen. Man kann auch
argumentieren, dass das semantisch besser ist.
> Es ist völlig egal, welche Frequenz die Aufrufe in den einzelnen Threads
> haben. Solange es keine "happens-before"-Beziehung zwischen Aktion A in
> Thread 1 und Aktion B in Thread 2 gibt, hast Du keinerlei Garantie, dass
> Thread 2 die Resultate von Aktion A je sehen wird, egal, wieviel
> Realzeit zwischen den Aktionen liegt.
Ja. Das ist die Theorie. Schreib-Lese-Konflikte. In dem Fall allerdings
nicht praktisch relevant. Das Blockadepotential der Anwendung ist eine
Größenordnung kleiner als die Wahrnehmung der Spieler. Und wenn externe
Blockaden auftreten, bleibt sowieso irgendwas unzumutbar stehen.
Weil es mir aber weniger darum geht, eine funktionierende Schachuhr zu
schreiben, als das Problem theoretisch zu verstehen und gewissermaßen
optimal zu lösen, kümmere ich mich natürlich auch um solche Konflikte.
lg
QNo
[toc] | [prev] | [next] | [standalone]
| From | Patrick Roemer <sangamon@netcologne.de> |
|---|---|
| Date | 2016-07-15 23:44 +0200 |
| Message-ID | <nmblfr$1ek$1@newsreader4.netcologne.de> |
| In reply to | #12996 |
Responding to Christian H. Kuhn: > Am 15.07.2016 um 22:04 schrieb Patrick Roemer: >> Alternativ/zusätzlich könnte man das Design so ändern, dass der >> ChessTimer nur eine Methode #checkFlag() hat, die von einem *externen* >> Timer aufgerufen wird. Dann braucht man "weiter oben" natürlich wieder >> eine Entität, die externen Timer und ChessTimer zusammenführt und muss >> die separat testen - aber man hat die Verantwortlichkeiten getrennt und >> die "Problemfläche" für Threading reduziert. > > Der Gedanke kam mir auch schon. Die Kernfunktionalität von QChessTimer > ist einfach das unterbrechbare Runterzählen und Ausliefern der Zeit. Ob > die Null erreicht ist, kann auch QChessClock prüfen. Man kann auch > argumentieren, dass das semantisch besser ist. Das wäre noch eine andere Variante. Ich meinte eher, dass QChessTimer zunächst mal eine State Machine ist, die anhand von eingehenden Events zwischen Idle, Stopped, Started und Flagged o.s.ä. wechselt. Momentan verwaltet die Klasse zusätzlich noch eine der Eventquellen (den Timer). Die könnte man da komplett rausziehen. Dann hätte man eine (quasi "rein funktionale") Klasse mit der Logik für die Zustandsübergänge. Und die könnte man in einer "übergeordneten" Klasse (die dem jetzigen QChessTimer entspricht) mit einer Timerabstraktion zusammenschalten. >> Es ist völlig egal, welche Frequenz die Aufrufe in den einzelnen Threads >> haben. Solange es keine "happens-before"-Beziehung zwischen Aktion A in >> Thread 1 und Aktion B in Thread 2 gibt, hast Du keinerlei Garantie, dass >> Thread 2 die Resultate von Aktion A je sehen wird, egal, wieviel >> Realzeit zwischen den Aktionen liegt. > > Ja. Das ist die Theorie. Schreib-Lese-Konflikte. In dem Fall allerdings > nicht praktisch relevant. Das Blockadepotential der Anwendung ist eine > Größenordnung kleiner als die Wahrnehmung der Spieler. Und wenn externe > Blockaden auftreten, bleibt sowieso irgendwas unzumutbar stehen. Ich dachte mir schon, dass Schachspieler eher so die geduldigen und sanguinischen Typen sind, aber dass sie es entspannt hinnehmen, wenn im Turnierfinale irgendwas (außer ihrer Deckung, natürlich) unzumutbar stehenbleibt, erstaunt mich dann doch. :D Abgesehen davon: Das ist nicht nur Theorie, das passiert. In echt. Und das konkret angesprochene Problem hat (erst mal) nix mit Blockaden und Konflikten zu tun. Es kann schlicht vorkommen, dass der Timer flagFallen auf true setzt, und der Main-Thread da nie etwas von mitbekommt. Und somit die Uhr unzumutbar *nicht* stehenbleibt... Viele Grüße, Patrick
[toc] | [prev] | [next] | [standalone]
| From | "Christian H. Kuhn" <qno-news@qno.de> |
|---|---|
| Date | 2016-07-16 23:44 +0200 |
| Subject | Countdown Timer Design (was: JUnit Test von JButton: Action wird nicht erkannt) |
| Message-ID | <duvo7iF6tgdU1@mid.individual.net> |
| In reply to | #12999 |
Am 15.07.2016 um 23:44 schrieb Patrick Roemer: > Responding to Christian H. Kuhn: >> Der Gedanke kam mir auch schon. Die Kernfunktionalität von QChessTimer >> ist einfach das unterbrechbare Runterzählen und Ausliefern der Zeit. Ob >> die Null erreicht ist, kann auch QChessClock prüfen. Man kann auch >> argumentieren, dass das semantisch besser ist. > > Das wäre noch eine andere Variante. > > Ich meinte eher, dass QChessTimer zunächst mal eine State Machine ist, > die anhand von eingehenden Events zwischen Idle, Stopped, Started und > Flagged o.s.ä. wechselt. Momentan verwaltet die Klasse zusätzlich noch > eine der Eventquellen (den Timer). Die könnte man da komplett > rausziehen. Dann hätte man eine (quasi "rein funktionale") Klasse mit > der Logik für die Zustandsübergänge. Und die könnte man in einer > "übergeordneten" Klasse (die dem jetzigen QChessTimer entspricht) mit > einer Timerabstraktion zusammenschalten. Ok. Konkret: Zwei Zustände, started und stopped. idle === stopped. flagged ist für QChessTimer kein eigener Zustand mehr, sondern wird höheren Ortes angesiedelt. Da wird einfach überprüft, ob die verbleibende Zeit noch > 0 ist, ansonsten Blättchen. Damit entfällt der TimerTask. Zustandsübergang durch start() und stop(). Eigentlich unparametrisiert, weil ja auf die Systemzeit zurückgegriffen wurde. Dann auch mit Parameter, damit der zu stoppende QChessTimer „zur gleichen Zeit“ angehalten werden kann wie der zu startende gestartet wird. Wenn einfach start() und stop() hintereinander ausgeführt werden, kann da schon mal ne ms oder vier dazwischen liegen. Verlängert die Partie nur um Sekundenbruchteile, aber es soll ja sauber sein. Also wird die unparametrisierte Variante gestrichen. Auf eine Zeitquelle muss nur noch zur Zeitabfrage bei laufender Uhr zurückgegriffen werden. Den Vorteil einer Abstraktion der Zeitquelle habe ich noch nicht erkannt. Im praktischen Einsatz wird dies die Systemuhr sein, eine Alternative kann ich mir gerade nicht vorstellen. Und dann würde ich überflüssige Funktionalität entwickeln, nur damit ich die testen kann ... Die Zeitquelle im QChessTimer könnte übrigens komplett entfallen, wenn das Auslesen des Zustands nicht die Zeit, sondern ein Objekt übergibt, das verbleibende Zeit und Zustandsname enthält. Bei laufender Uhr wird remainingTime + startTime zurückgegeben; wenn der Zustand running ist, muss der Aufrufer sehen, woher er die aktuelle Zeit nimmt, zieht die ab und hat die verbleibende Zeit; bei stopped bekommt er schon den korrekten Wert. QChessTimer würde also endgültig nichts mehr mit laufender Zeit zu tun haben, sondern nur noch von außen mitgeteilte Marken verwalten. Ob man tatsächlich intern die Zeit in ns führt, aber nach außen s ausgibt ... da ist ein Faktor von einer Million dazwischen. Es könnte reichen, intern in ms zu rechnen. Problem: Beim Test, ob die Uhr läuft, könnten zwei Abfragen hintereinander so schnell aufeinanderfolgen, dass die ms sich nicht verändert haben und ein Test falsch fehlschlägt. Damit sind die Problempunkte aus QChessTimer herausgelöst. Zugriff erfolgt von QChessClock, aber bis zum Beweis des Gegenteils aus mehreren Threads: Einem main-Thread, der die Timer erzeugt (reset erfolgt über neuen Konstruktor), Knopfdruckaktionen können in einem eigenen Thread, z.B. Swing-EDT, ausgelöst werden, und ein möglicher eigener Thread zur Aktualisierung der Restzeit und der Fallblättchenüberprüfung. QChessTimer muss also thread safe geschrieben werden. Zugriffe auf int, long und boolean sind atomar. Die vier verbleibenden paketöffentlichen Funktionen müssten synchronized werden. Aber jetzt erstmal schlafen. Threadsicher und ohne interrupts ;-) lg QNo
[toc] | [prev] | [next] | [standalone]
| From | "Christian H. Kuhn" <qno-news@qno.de> |
|---|---|
| Date | 2016-07-17 12:44 +0200 |
| Subject | Re: Countdown Timer Design |
| Message-ID | <dv15t5Fgp04U1@mid.individual.net> |
| In reply to | #13003 |
Am 17.07.2016 um 03:01 schrieb Stefan Ram:
> "Christian H. Kuhn" <qno-news@qno.de> writes:
> Ich würde das vielleicht so machen (habe aber im Moment keine Zeit,
> es wirklich zu schreiben):
Ein interessantes, weil sehr einfaches Design. Anmerkungen:
> public void start( final long point )
> { if( this.isRunning )throw new java.lang.IllegalStateException();
Hier und bei end() ist die Exception wohl nicht nötig. Wenn der Knopf
von außen mehrfach gedrückt wird – und das kommt in der Praxis durchaus
vor – ist es unnötig, da nochmal ausdrücklich darauf hinzuweisen. Es
passiert einfach nichts. Eine watch clause if (this.isRunning) {return}
oder andersrum if (!this.isRunning) { Body } reicht völlig.
> public long duration( final long point )
> { return this.duration +
> ( this.isRunning ? point - this.startTime : 0 ); }}
Wird nicht unbedingt funktionieren. Funktioniert dann, wenn die
Oberklasse „oft genug“ aktualisiert und bei 0 die gespeicherte duration
weiter verwenden kann. Das kann aber nicht garantiert werden, wenn
zwischen zwei Aktualisierungen längere Zeit liegt, die zuerst laufende
Uhr inzwischen angehalten wurde und mehr Zeit vergangen ist als die
kleinste im Display dargestellte Zeiteinheit.
> Beim Aufbauen der Swing-Oberfläche wird ohnehin
> invokeAndWait verwendet (S), alle anderen Ereignisse sollten
> von Swing kommen und damit schon auf dem EDT laufen.
Für QChessTimer und QChessClock keine valide Annahme. Hier soll ein
unabhängiges Modell entworfen werden, auf das beliebige Oberflächen
aufgesetzt werden können, von der Kommandozeile und Tastendrücken über
Swing und JavaFX bis hin zu Weboberflächen, bei denen Uhr und beide
Spieler auf drei verschiedenen Systemen arbeiten.
> S: Es wird mit »clock = new Clock()« eine neue Uhr erzeugt
> (ich nehme zur Vereinfachung nur eine Uhr an).
>
> A: Es wird »clock.start( now() )« aufgerufen, wobei »now()«
> die aktuelle Zeit in (beispielsweise) Nanosekunden ist.
>
> O: Es wird »clock.end( now() )« aufgerufen.
Das übernimmt im Modell QChessClock und wird von der Oberfläche
irgendwie gesteuern.
> R: Ich nehme an, daß Android und Swing es erlauben, sich
> regelmäßig Timer-Ereignisse schicken zu lassen, am besten
> synchronisiert mit der Bildschirmauffrischung und diese auch
> gleich auf dem EDT laufen.
Muss wie gesagt in Java passieren, weil die Technologie der Oberfläche
an dieser Stelle unbekannt ist.
> Dann wird mit »clock.duration( now() )«
> die aktuelle Zeit der Uhr abgefragt und angezeigt.
Ah. DAS ist die Rettung :-) Wenn man beim Abfragen die Zeit mit
übergibt, kann man auf jeden Fall die verbleibende Zeit zurückgeben,
egal ob running oder stopped. Und QChessTimer braucht keine Zeitquelle mehr.
> Ich würde allerdings gerade bei einem »Lernprojekt« dann
> auch JavaFX verwenden, da man ja für die Zukunft lernen will
> und nicht für die Vergangenheit.
Werde ich mir anschauen. Der recht knappe deutsche Wikipedia-Artikel
behauptet, JavaFX sei für Rich Internet Applications zuständig. Zur Zeit
nicht mein Thema, kann aber noch kommen. Und auch wenn eine Web-GUI
möglich sein soll, geht es mir doch erstmal um lokale Anwendungen. Daher
Swing zum Lernen, Android für die Produktion.
Der umfangreichere englische Artikel erklärt JavaFX aber auch für
Desktop Applications zuständig. Das erklärt auch, warum JavaFX Swing
ablösen soll; das ist in der dt. Fassung zwar formuliert, klingt da aber
unlogisch. Ich werde mir JavaFX auf jeden Fall anschauen, und vermutlich
probiere ich dann auch eine GUI für QChessClock. Aber erst nach Android.
lg
QNo
[toc] | [prev] | [next] | [standalone]
| From | "Christian H. Kuhn" <qno-news@qno.de> |
|---|---|
| Date | 2016-07-17 15:49 +0200 |
| Subject | Re: Countdown Timer Design |
| Message-ID | <dv1go6Fjau2U1@mid.individual.net> |
| In reply to | #13003 |
Am 17.07.2016 um 12:35 schrieb Stefan Ram: > ram@zedat.fu-berlin.de (Stefan Ram) writes: > Die Modellklassen wurden nun um eine Klasse »Clock« > erweitert, die zwei Uhrwerke »Unit« enthält. > Damit kann sie eine vereinfachte Art von Schachuhr > imitieren. Warum sieht das bei dir soviel aufgeräumter auf als bei mir? :-) Ok, du hast die Zeitquelle jetzt auch noch aus Clock herausgezogen und in die Verantwortung des Benutzers, also des UI, gegeben. Kann man so machen. Es gibt ein Argument, das dagegen spricht. Ich bin der Auffassung, dass die Feststellung einer Zeitüberschreitung entweder in Unit oder in Clock gehört. Unit kann ein allgemeiner Timer sein, der über Zeitüberschreitung nichts zu wissen braucht, dann gehört der Überschreitungsalarm nach Clock. Deine Unit zählt die Zeit hoch, ich brauche eigentlich einen Countdown, das ist dann eine Zeile mehr, und dann kann man auch argumentieren, dass das Erreichen der 0 von Unit festgestellt werden muss. Geht beides, ich neige im Moment mehr dazu, das in Clock zu tun. Wenn ich aber die Zeitüberschreitung in Clock feststelle, kann ich das eigentlich nur machen, indem ich regelmäßig duration von beiden Unit abfrage. Nicht nur von der laufenden, weil ja die andere Uhr zwischen letzter Abfrage und letztem toggle die Zeit überschritten haben kann. Damit muss Clock eine eigene Vorstellung von now haben. Die Alternative wäre, dass das UI getaktet Clock.isFlagFallen(now()) aufrufen müsste. Hm. Bauch sagt nein ... Das wird eine grundsätzliche Design-Frage. Ich folge bislang dem Observer-Muster: Clock als Observable verwaltet die eigenen Zustände und benachrichtigt seine Observer über Änderungen. Das (G)UI ist dumm. Es trifft keine Feststellungen über Zustände und Übergänge, es stellt einfach dar, was es mitgeteilt bekommt. Indem du Clock derart vereinfacht hast, wird mehr Logik in das (G)UI verfrachtet. Die Frage, wie oft auf welche Zustandsänderungen überprüft wird, ist sehr abhängig von der Implementation von Clock. Im Moment mag das kein Problem sein. Sobald der volle Funktionsumfang einer Turnierschachuhr (mehrere verschiedene Bedenkzeitperioden, Zugzähler, Bronstein-Bedenkzeit, Editierfunktionen für den Schiedsrichter) implementiert wird, befürchte ich eine Verletzung der Kapselung. lg QNo
[toc] | [prev] | [next] | [standalone]
| From | "Christian H. Kuhn" <qno-news@qno.de> |
|---|---|
| Date | 2016-07-17 17:03 +0200 |
| Subject | Re: Countdown Timer Design |
| Message-ID | <dv1l37Fkc9eU1@mid.individual.net> |
| In reply to | #13008 |
Am 17.07.2016 um 16:09 schrieb Stefan Ram:
> Ich wußte nicht, daß Schachuhren abwärts zählen, aber werde
> das dann zukünftig berücksichtigen.
Früher taten sie es nicht. In der analogen Welt waren das zwei ganz
normale Uhrenwerke. Die Uhr lief vorwärts. Ein paar Minuten vor der
vollen Stunde begann der Minutenzeiger (bei ein paar speziellen Modellen
auch ein anderer Mechanismus), das Fallblättchen anzuheben. Bei
Erreichen der vollen Stunde verlor das Blättchen seinen Halt auf dem
Zeiger und fiel wieder in die Ruhestellung. Eine Konvention, die mäßig
eingehalten wurde, verlangte, dass die Uhr so zu stellen sei, dass um
06:00:00 Uhr Partieende ist. Also wird für Blitzschach (5min pro Spieler
und Partie) die Uhr auf 05:55:00 gestellt. Für die übliche Bedenkzeit
bei Amateur-Turnierpartien (2h für die ersten 40 Züge, 1h für den Rest)
dann auf 03:00:00).
Interessant war aber nie die Uhrenstellung, sondern die verbleibende
Zeit. Dem Blitzspieler war es egal, ob da jetzt 5:58:32 oder 10:58:32
angezeigt wird. Wichtig war die Info: 1:28 min verbleiben. Mit dem
Aufkommen digitaler Schachuhren (die auch erst moderne Bedenkzeiten mit
Zeitzuschlägen pro Zug ermöglichten) ging man dann dazu über, die
verbleibende Bedenkzeit anzuzeigen und folglich rückwärts zu zählen.
Da Displays offensichtlich teuer sind, ist es zur Zeit bei den
handelsüblichen Modellen üblich, dass die Zeit zunächst in hh:mm
angezeigt wird. Sobald eine Restzeit von (je nach Modell) 10 oder 20
Minuten unterschritten wird, wechselt die Anzeige auf mm.ss. So spart
man zwei Stellen ein.
Das Fallblättchen wird durch ein Sonderzeichen, z.B. durch einen
führenden Unterstrich oder einen Apostroph, angezeigt. Bei
Bedenkzeitregelungen mit mehreren Zeitperioden kann es für jede Periode
ein eigenes Blättchen geben. Es kann aber auch kurz vor der nächsten
Zeitkontrolle das alte gelöscht und wiederverwertet werden.
Manche Uhren zeigen an, in der wievielten Bedenkzeitperiode man sich
befindet. Manche Uhren zeigen an, auf welcher Seite Weiß sitzt. Alle
Uhren zeigen an, welche Seite am Zug befindlich ist; entweder durch das
Blinken des Trennzeichens zwischen den hh und mm bzw. zwischen mm und
ss, das dann auch das Laufen der Uhr anzeigt, oder durch ein
zusätzliches Zeichen im Display, z.B. einen Pfeil, oder auch durch eine
Leuchte am Gehäuse.
> Da kenne ich mich eben nicht aus, also kann ich so etwas
> auch nicht implementieren. Ich habe lediglich eine Pause
> beider Uhrenwerke vorgesehen, falls beide Spiele mit dem
> Schiedsrichter diskutieren wollen, da ich davon im Web
> gelesen hatte.
Ein durchaus realistisches Szenario. Und damit weisst du in dem Punkt
mehr als 80% der Schachspieler, die die Uhr für Diskussionen nicht
anhalten und sich danach über die abgelaufene Bedenkzeit beschweren :-)
Du hast also den durchaus richtigen Ansatz verfolgt, dass man erstmal
die aktuellen Anforderungen implementiert und sein Design nicht an
möglichen zukünftigen Anforderungen orientiert. Ich habe es hier nicht
kommuniziert, weiss aber, dass da noch mehr kommt. Und bevor ich schon
wieder alles umschreibe, behalte ich das von Anfang an im Kopf.
> Bei einer reinen Standard-Java-Konsolenoberfläche, die nur
> einfache Methoden in der Art wie »println« vorsieht, müßte
> man dann in einer Schleife immer wieder die neuen Zeiten
> ausgeben. Außerdem benötigt man noch die Möglichkeit
> /nichtblockierend/ zu testen, ob der Bediener eine Taste
> gedrückt hat.
Geht wohl nicht in einem Thread. Scheint aber kein Problem zu sein, wenn
ein Thread ausgibt und ein Thread blockierend wartet:
public class Threadspiel {
public static void main(final String[] _args) {
Thread thread1 = new Thread(() -> {
while (true) {
System.out.print("a");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
}
});
Thread thread2 = new Thread(() -> {
while (true) {
try {
if (0 < System.in.available()) {
System.out.println(System.in.read());
}
} catch (IOException e) {
}
}
});
thread1.start();
thread2.start();
}
}
lg
QNo
[toc] | [prev] | [next] | [standalone]
| From | "Christian H. Kuhn" <qno-news@qno.de> |
|---|---|
| Date | 2016-07-19 15:59 +0200 |
| Subject | Re: Countdown Timer Design |
| Message-ID | <dv6q3vFrm36U1@mid.individual.net> |
| In reply to | #13011 |
Am 18.07.2016 um 00:22 schrieb Stefan Ram: > Inzwischen habe ich mein Model mal mit einer einfachen > JavaFX-GUI verbunden. Das sollte mit dem JDK 8 direkt > kompiliert und gestartet werden können, wenn es in eine > Datei »Main.java« kopiert wird. Ich habe ein wenig gekämpft, bis Eclipse bereit war, da irgendwas zu starten. Insbesondere hat es eine main()-Methode gebraucht. Aber dann lief es. Und mit der reduzierten Funktionalität ist es schon fast im echten Einsatz für Blitzschach zu gebrauchen. Halt etwas intuitivere Zeitanzeige und ein Knopf für neue Partie :-) Zu den Der JavaFX-Code liest sich gut. Da werde ich mich wirklich mal reinarbeiten müssen. Zunächst sieht der Ansatz mit dem ApplicationTimer natürlich nett aus. Über Gründe dafür, die Refresh-Taktung in der Clock zu belassen, habe ich woanders im Thread ausführlich erläutert. lg QNo
[toc] | [prev] | [next] | [standalone]
| From | "Christian H. Kuhn" <qno-news@qno.de> |
|---|---|
| Date | 2016-07-19 14:59 +0200 |
| Message-ID | <dv6mjhFqq9cU1@mid.individual.net> |
| In reply to | #12993 |
Version 0.9.2
Am 15.07.2016 um 22:04 schrieb Patrick Roemer:
> Responding to Christian H. Kuhn:
> interface TimeSource {
> long getTimeInNanoSecs();
> }
Den Gedanken habe ich aufgegriffen und mal implementiert. Die TimeSource
darf sich dabei aussuchen, welche Zeiteinheit sie verwendet; es muss nur
ein ganzzahliger Bruchteil einer Sekunde sein.
public interface QTimeSource {
long getFactorToSeconds(); // erlaubt versch. time units
long getNow();
}
public class QSystemMilliTime implements QTimeSource {
private static final long MILLIS_PER_SECOND = 1000;
@Override public final long getFactorToSeconds() {
return MILLIS_PER_SECOND;
}
@Override public final long getNow() {
return System.currentTimeMillis();
}
}
private static class TestTimeSource implements QTimeSource {
private static final long SECONDS_TO_SECONDS = 1;
private long now;
@Override public long getFactorToSeconds() {
return SECONDS_TO_SECONDS;
}
@Override public long getNow() {
synchronized (this) {
return now;
}
}
public void setNow(final long _now) {
synchronized (this) {
now = _now;
}
}
}
Dazu habe ich Stefan Rams Timer-Konzept im großen und ganzen übernommen
und mit synchronize(this) (hoffentlich?) thread safe gemacht. Der Timer
arbeitet jetzt mit abstrakten Zeiten. Zur Sicherheit müsste im
Konstruktor die startTime noch auf ein adäquates now gesetzt werden,
falls von start(long) auch Werte <= 0 kommen könnten.
public final class QCountdownTimer {
private transient long remainingTime;
private transient long additionalFischerTime;
private transient long startTime;
private transient long stopTime;
private transient boolean running;
QCountdownTimer(final long _timeOfReflection) {
this(_timeOfReflection, 0);
}
QCountdownTimer(final long _timeOfReflection, final long
_additionalFischerTime) {
running = false;
additionalFischerTime = _additionalFischerTime;
remainingTime = _timeOfReflection + additionalFischerTime;
}
/* default */ long getRemainingTime(final long _now) { // Parameter
has no effect if !running
long actualRemaningTime;
synchronized (this) {
if (running && _now > startTime) {
actualRemaningTime = remainingTime - _now + startTime;
} else {
actualRemaningTime = remainingTime;
}
return actualRemaningTime;
}
}
/* default */ void start(final long _startTime) {
synchronized (this) {
if (!running && _startTime >= stopTime) {
running = true;
startTime = _startTime;
}
}
}
/* default */ void stop(final long _stopTime) {
synchronized (this) {
if (running && _stopTime >= startTime) {
running = false;
stopTime = _stopTime;
remainingTime = remainingTime - (stopTime - startTime) +
additionalFischerTime;
}
}
}
/* default */ boolean isRunning() {
synchronized (this) {
return running;
}
}
}
QChessClock kann jetzt im Konstruktor eine QTimeSource mitbekommen,
ansonsten nimmt sie QSystemMilliTime.
> ...und für Tests dann sowas:
Genau.
> Das ist aber nur die halbe Miete. Man würde dann auch den konkreten
> j.u.Timer in der Kernklasse wegabstrahieren wollen.
Siehe den langen Subthread „Countdown Timer Design“ mit vielen Beiträgen
von Stefan Ram. Dem bin ich in der ersten Stufe gefolgt: QCountdownTimer
weiss nichts von Zeitquellen und hat auch keinen TimerTask mehr. Der
konkrete Zeitbegriff und die Überprüfung auf Zeitablauf erfolgt im
Verwender.
In einem Punkt stimme ich mit Stefan nicht überein. Er hat auch die
Schachuhr von TimeSource und TimerTask befreit. In der einfachen
Blitzschachuhr, die er benutzt, ist das ok. Da wird bei Blättchenfall
einfach nur eine Anzeige gesetzt und gut.
Abweichend vom sauberen TDD, siehe Beiträge von Wanja Gayk im Thread
„Unit-Tests von Einheiten ohne öffentliche Leseschnittstelle“, weiss ich
aber jetzt schon, dass in der nächsten Ausbaustufe beim Blättchenfall
zusätzliche Funktionalität dazukommt. Die ist in der Begrifflichkeit des
Kano-Modells kein Begeisterungs- oder auch nur Leistungs-, sondern
Basis-Merkmal und wird definitiv im ersten Release enthalten sein. Also
darf ich auch ohne Test in die Richtung planen ;-)
Es geht um mehrere Bedenkzeitperioden. Bislang implementiert sind
Bedenkzeitmodelle mit einer Periode. Ist die Zeit für diese Periode
abgelaufen, ist die Partie im Prinzip vorbei. Die Zeitüberschreitung
wird angezeigt, mehr Funktionalität ist nicht nötig. Das wird in der
Praxis bei sogenannten Blitz- und Rapid-Partien angewandt und betrifft
typischerweise kurze Bedenkzeiten von 1 bis 90 min pro Spieler und
Partie, mit Zeitzuschlägen von 1 bis 20 s.
In „langen“, „ernsten“, „Turnier-“ Partien werden üblicherweise zwei
oder mehr Zeitperioden benutzt. In den unteren Amateurklassen ist sehr
verbreitet: 2 h für die ersten 40 Züge, danach ein Zuschlag von einer
oder einer halben Stunde. Die Bundesliga spielte von 2009 bis 2014 mit
100 min für die ersten 40 Züge, 50 min für die nächsten 20 Züge, 15 min
für den Rest, 30s akkumulierender Zuschlag pro Zug.
Die letzte Zeitperiode wird gehandhabt wie bisher: Bei Ablauf Anzeige
und gut. Die Zeitperioden davor machen mehr Arbeit. Bei Ablauf der
Periode (in der Regel durch Bedenkzeitüberschreitung, gelegentlich auch
durch Erreichen der Zügezahl, wofür noch ein Zugzähler zu implementieren
ist) wird die Zeitüberschreitung beim jeweiligen Spieler so angezeigt,
dass sie über einen längeren Zeitraum festgestellt werden kann.
Gleichzeitig wird die erlaubte Bedenkzeit der nächsten Periode bei
beiden Spielern zur aktuellen Bedenkzeit addiert.
Ich bin ja der Ansicht, dass diese Umstellung der verbleibenden
Bedenkzeiten ein Wissen über Implementationsdetails der Schachuhr
verlangt. Das ist vor anderen Klassen zu verbergen und gehört damit in
die Schachuhr. Auch die regelmäßige Überprüfung auf Zeitüberschreitung
darf nicht anderen Klassen überlassen werden. Sie ist von der Schachuhr
selbst zu leisten; entsprechende Ereignisse sind an Oberflächen zu
senden. Damit benötige ich einen TimerTask in der Schachuhr, ein
ScheduledThreadPoolExecutor wäre ne Nummer zu dick. Der TimerTask
überprüft in sinnvollen Abständen auf ZÜ. Bei Anzeigengenauigkeit von 1s
wäre ein Takt von 1s zu langsam; der durchschnittliche Fehler von einer
halben Sekunde ist beim Blitzen bereits relevant. Der Millisekunden-Takt
der Standard-Zeitquelle würde sehr genau anzeigen, aber eine nicht mehr
wahrnehmbare Genauigkeit produzieren. Der Mensch kann Bilder in Folgen
von 24/s wahrnehmen, Sicherheitsfaktor 2, runden, sind wir bei 1/50 s
oder 20 ms. Und wenn dieser Thread schon mal existiert, kann er auch
veränderte Zeiten an die GUI mitteilen.
> Über SwingUtilities#invokeLater().
Mit den beiden invoke bin ich auch am Kämpfen. Beide sorgen auf dem EDT
für eine klare Abfolge von Events. Nur invokeAndWait() sorgt aber auch
für eine happens-before-Beziehung zwischen ausgeführten Befehlen auf dem
aufrufenden Thread und dem EDT. Für manche Tests nicht unwichtig, die
darauf warten müssen, dass das EDT-Ereignis auch ausgeführt ist, bevor
der Test funktionieren kann. Dafür wirft invokeAndWait() eine Exception.
In normalem Code kann die problemlos abgefangen und verarbeitet werden;
in Runnables und actionPerformed muss man entweder eine try-catch mit
leerem Catch-Block machen, was alle statischen Codechecker anmeckern,
oder man macht im Catch-Block so Dinge wie
final Thread thread = Thread.currentThread();
thread.getUncaughtExceptionHandler().uncaughtException(thread, e);
die auch keinen Schönheitspreis gewinnen.
lg
QNo
[toc] | [prev] | [next] | [standalone]
| From | "Christian H. Kuhn" <qno-news@qno.de> |
|---|---|
| Date | 2016-07-19 16:06 +0200 |
| Message-ID | <dv6qfsFroppU1@mid.individual.net> |
| In reply to | #13019 |
Am 19.07.2016 um 14:59 schrieb Christian H. Kuhn: [Refresh-Takt in UI oder Model] Auch hier noch einen: Prinzipiell könnte eine QChessClock mehrere Observer haben. Hochwertige elektronische Schachuhren haben heute schon eine Schnittstelle, über die die angezeigte Zeit auch im Internet abgefragt werden kann. Wenn die Refresh-Taktung in der UI stattfindet, bekomme ich ein schönes Durcheinander und möglicherweise vorübergehende Inkonsistenz der Anzeigen. Mit Refresh-Takt im Model vermeide ich das. Da bemerke ich, dass bei mehreren Observern die notify-Methoden aufeinander warten. Es könnte u.U. sinnvoll sein, doch einen ThreadPool aufzumachen und für jeden Observer einen Thread anzulegen, auf dem der seine Benachrichtigungen bekommt, ohne dass schnellere Observer warten müssen ... lg QNo
[toc] | [prev] | [next] | [standalone]
| From | Patrick Roemer <sangamon@netcologne.de> |
|---|---|
| Date | 2016-07-19 18:59 +0200 |
| Message-ID | <nmlm94$vfk$1@newsreader4.netcologne.de> |
| In reply to | #13019 |
Responding to Christian H. Kuhn: >> Über SwingUtilities#invokeLater(). > > Mit den beiden invoke bin ich auch am Kämpfen. Beide sorgen auf dem EDT > für eine klare Abfolge von Events. Nur invokeAndWait() sorgt aber auch > für eine happens-before-Beziehung zwischen ausgeführten Befehlen auf dem > aufrufenden Thread und dem EDT. Nein! Der Unterschied ist, dass #invokeLater() asynchron ist, d.h. der Aufruf kehrt "sofort" zurück, und der übergebene Codeblock wird dann irgendwann später auf dem EDT ausgeführt. #invokeAndWait() ist synchron, d.h. der Aufruf kehrt erst zurück, wenn der Codeblock auf dem EDT tatsächlich ausgeführt wurde. Wenn kein besonderer Anlass für die Verwendung von #invokeAndWait() vorliegt, ist #invokeLater() zu bevorzugen. In beiden Fällen entsteht *keine* happens-before-Beziehung zwischen irgendwas. In dem Beispielschnipsel, den ich neulich hier hingeklebt habe, hat man eine happens-before-Beziehung zwischen dem Schreiben in die AtomicReference auf dem EDT und dem Lesen dieses Wertes auf dem main-Thread. Und die entsteht eben durch die Verwendung von AtomicReference, weil der Wert darin volatile gehalten wird. Im Prinzip könnte man #invokeAndWait() z.B. dadurch emulieren, dass man am Ende des #invokeLater() übergebenen Blocks ein CountdownLatch triggert und direkt nach dem Aufruf von #invokeLater() auf dieses Latch wartet. Dann hätte man tatsächlich eine happens-before-Beziehung zwischen diesen beiden Latch-Interaktionen. Wenn das noch fuzzy ist, war eine Stunde in der S-Bahn offenkundig nicht ausreichend. :) > Für manche Tests nicht unwichtig, die > darauf warten müssen, dass das EDT-Ereignis auch ausgeführt ist, bevor > der Test funktionieren kann. Dafür wirft invokeAndWait() eine Exception. > In normalem Code kann die problemlos abgefangen und verarbeitet werden; > in Runnables und actionPerformed muss man entweder eine try-catch mit > leerem Catch-Block machen, was alle statischen Codechecker anmeckern, > oder man macht im Catch-Block so Dinge wie > > final Thread thread = Thread.currentThread(); > thread.getUncaughtExceptionHandler().uncaughtException(thread, e); > > die auch keinen Schönheitspreis gewinnen. Das klingt auch noch etwas konfus. In beiden Fällen kann es passieren, dass der übergebene Block eine Exception wirft. Im Fall von #invokeAndWait() wird diese dann, in eine InvocationTargetException gewickelt, an den Aufrufer propagiert. Im Fall von #invokeLater() hat man diese Option nicht, denn der Block wird ja erst ausgeführt, nachdem der Aufruf zurückgekehrt ist. Also bleibt nichts, als diese Exception *im EDT* hochblubbern zu lassen, was wahrscheinlich auch eher unerwünscht ist. Um Exceptions im übergebenen Block muss sich also in jedem Fall Gedanken machen. Das Problem mit leeren catch-Blöcken ist sicher nicht rimäar, dass Codechecker das nicht mögen. Und was ein #invokeAndWait() in einem #actionPerformed() suchen könnte, will ich glaube ich gar nicht so genau wissen... ;) Viele Grüße, Patrick
[toc] | [prev] | [next] | [standalone]
| From | "Christian H. Kuhn" <qno-news@qno.de> |
|---|---|
| Date | 2016-07-19 22:35 +0200 |
| Message-ID | <dv7haoF2pteU1@mid.individual.net> |
| In reply to | #13022 |
Am 19.07.2016 um 18:59 schrieb Patrick Roemer:
> Responding to Christian H. Kuhn:
> In beiden Fällen entsteht *keine* happens-before-Beziehung zwischen
> irgendwas.
JETZT bin ich verwirrt.
Ich war der Ansicht, bei
commandA();
SwingUtilities.invokeAndWait(() -> {commandB});
commandC();
SwingUtilities.invokeAndWait(() -> {commandD});
könnte ich mich darauf verlassen, dass die Reihenfolge A-B-C-D
eingehalten wird, sofern A und C nur auf dem gerade laufenden
aufrufenden Thread arbeiten, während bei dem entsprechenden Aufruf mit
invokeLater() lediglich sicher ist, dass A vor C (auf dem Hauptthread)
und B vor D (auf dem Swing-EDT) ausgeführt wird?
> Und was ein #invokeAndWait() in einem
> #actionPerformed() suchen könnte, will ich glaube ich gar nicht so genau
> wissen... ;)
Nix direktes. Warum das eher dämlich wäre, habe ich schon begriffen :-)
Es gibt aber Methoden, die könnten theoretisch vom EDT aus aufgerufen
werden, dann bräuchten sie kein invoke...(). Oder von irgendeinem
anderen Thread. Dann brauchen sie invoke. Den Swing-EDT kann man
feststellen. Die throw-Klausel hat man aber trotzdem, wenn man die
Exception des invoke nicht fangen mag. Das kann auch eine beliebig lange
Kette von Methodenaufrufen sein. Und wenn keine dieser Methoden etwas
Sinnvolles mit der Exception aus invokeAndWait() anfangen kann, landet
die irgendwan in actionPerformed().
lg
QNo
[toc] | [prev] | [next] | [standalone]
| From | Patrick Roemer <sangamon@netcologne.de> |
|---|---|
| Date | 2016-07-20 13:00 +0200 |
| Message-ID | <nmnll7$9uq$1@newsreader4.netcologne.de> |
| In reply to | #13023 |
Responding to Christian H. Kuhn:
> Am 19.07.2016 um 18:59 schrieb Patrick Roemer:
>> Responding to Christian H. Kuhn:
>> In beiden Fällen entsteht *keine* happens-before-Beziehung zwischen
>> irgendwas.
>
> JETZT bin ich verwirrt.
>
> Ich war der Ansicht, bei
>
> commandA();
> SwingUtilities.invokeAndWait(() -> {commandB});
> commandC();
> SwingUtilities.invokeAndWait(() -> {commandD});
>
> könnte ich mich darauf verlassen, dass die Reihenfolge A-B-C-D
> eingehalten wird, sofern A und C nur auf dem gerade laufenden
> aufrufenden Thread arbeiten, während bei dem entsprechenden Aufruf mit
> invokeLater() lediglich sicher ist, dass A vor C (auf dem Hauptthread)
> und B vor D (auf dem Swing-EDT) ausgeführt wird?
Gemäß wall-time ist dem auch so. Damit ist aber noch nicht garantiert,
dass C die Auswirkungen von Aktionen in B auch sieht. Und *das* ist,
worum es bei happens-before-Beziehungen geht.
>> Und was ein #invokeAndWait() in einem
>> #actionPerformed() suchen könnte, will ich glaube ich gar nicht so genau
>> wissen... ;)
>
> Nix direktes. Warum das eher dämlich wäre, habe ich schon begriffen :-)
> Es gibt aber Methoden, die könnten theoretisch vom EDT aus aufgerufen
> werden, dann bräuchten sie kein invoke...(). Oder von irgendeinem
> anderen Thread. Dann brauchen sie invoke. Den Swing-EDT kann man
> feststellen.
Klingt schon fishy. Wenn eine Methode aus unterschiedlichen Kontexten
aufgerufen werden kann, sollte der Kontext sie schlicht nicht
interessieren müssen. Das #invoke...() gehört dann in eine Schicht, die
den Kontext kennt, z.B. eben ins Presentation Model. Und Aufrufe, die
darüber laufen, sollten dann eben immer aus einem non-EDT-Thread kommen.
> Die throw-Klausel hat man aber trotzdem, wenn man die
> Exception des invoke nicht fangen mag.
Warum sollte man das nicht mögen? Eine InvocationTargetException will
man eigentlich nicht weiterpropagieren. Und eine InterruptedException
sollte man auch möglichst lokal behandeln.
> Das kann auch eine beliebig lange
> Kette von Methodenaufrufen sein. Und wenn keine dieser Methoden etwas
> Sinnvolles mit der Exception aus invokeAndWait() anfangen kann, landet
> die irgendwan in actionPerformed().
Das sollte eigentlich nicht passieren. Abgesehen davon: Ich kann mich
nicht erinnern, #invokeAndWait() je (außer eben in Tests) eingesetzt zu
haben. (Allerdings habe ich auch nur kleinere Hobbyprojekte mit Swing
gemacht.) Üblicherweise will man #invokeLater(). Aber auch da muss man
sich natürlich über eventuelle Exceptions Gedanken machen.
Viele Grüße,
Patrick
[toc] | [prev] | [next] | [standalone]
| From | "Christian H. Kuhn" <qno-news@qno.de> |
|---|---|
| Date | 2016-07-23 20:36 +0200 |
| Message-ID | <dvhrqpFh70cU1@mid.individual.net> |
| In reply to | #13026 |
Version 0.12.6 Am 20.07.2016 um 13:00 schrieb Patrick Roemer: > Gemäß wall-time ist dem auch so. Damit ist aber noch nicht garantiert, > dass C die Auswirkungen von Aktionen in B auch sieht. Und *das* ist, > worum es bei happens-before-Beziehungen geht. Nächster Versuch. Vielleicht habe ich es diesmal richtig verstanden. Ich nehme es mal als gutes Zeichen, dass ich eine Menge Code gestrichen habe :-) QChessClock: Auf Eigenschaften wird schreibend nur durch die Methoden von QChessClockInterface und durch addObserver() zugegriffen. Gelesen werden die Eigenschaften nur durch den Bau des Austauschobjekts in notifyObservers(). Damit sich diese Methoden nicht in die Quere kommen, werden sie mit synchronized (this) synchronisiert. Auf expliziten Aufruf von notifyObservers() nach jeder Zustandsänderung wird verzichtet. Der Zustand der Clock wird alle 20ms an die Observer verschickt. QChessClockJavaAV: GUI-Events und das folgende actionPerformed() sowie die darin aufgerufenen Methoden liegen schön brav nacheinander auf dem Swing-EDT. Diese Methoden führen nicht mehr zu einem notifyObservers() und damit zu einem update(). update() wird nur noch vom TimerTask der Clock via notifyObservers() aufgerufen und legt alle Zustandsänderungen der GUI per invokeLater auf den Swing-EDT. Mögliches Problem: Es könnte sein, dass die Clock einen Zustand annimmt, in dem ein bestimmter Methodenaufruf unzulässig oder unsinnig ist. Die Clock ist in diesem Zustand und teilt ihn beim nächsten Click an die GUI mit. setEnabled(false) für das entsprechende GUI-Element wird auf den Swing-EDT gelegt und dann auch irgendwann ausgeführt. In der Zeit zwischen Zustandsänderung der Clock und Sperren des Bedienelements könnte dieses ausgelöst worden sein und seinen in der Clock nicht mehr sinnvollen Befehl ausführen. Die Clock muss also unsinnige Eingaben abfangen. Meines Erachtens tut sie das schon. Ein künftiges Refactoring während der noch folgenden Zustandserweiterungen könnte möglicherweise eine Umstellung auf das State- oder Strategy-Entwicklungsmuster beinhalten. Normaler Ablauf: Drei Threads. Main erstellt die GUI. Swing-EDT führt GUI-Kommandos und GUI-Aktualisierungen aus. TimerTask benachrichtigt GUI über vorzunehmende Änderungen. GUI-Zustand und Clock-Zustand sind nicht unbedingt synchron. Im normalen Anwendungsfall ändert sich das aber spätestens durch den nächsten TimerTask. Wann genau der bzw. die von ihm per invokeLater() ausgelösten Änderungen der GUI wirksam sind, ist so nicht vorhersehbar. Ich bleibe aber dabei, dass das Risiko, dass ein notifyObservers() nicht innerhalb der 20ms bis zum nächsten Aufruf fertig abgeschlossen ist, für sehr überschaubar. Die Inkonsistenz wird also vom menschlichen Spieler nicht bemerkt. Tests: In Tests ist es etwas anderes. Aufeinanderfolgende Methodenaufrufe erfolgen so schnell hintereinander, dass notifyObservers() dazwischen eher nicht aufgerufen wird. Daher müssen hier Threads synchronisiert werden. QChessClockTest: Die Testklasse implementiert eine Oberfläche, also insbesondere update(). Die Tests verlaufen so, dass eine neue Clock mit spezieller, aus dem Test veränderbarer Zeitquelle erstellt wird. Der Test ruft die zu testende Bedienfunktion auf und/oder verändert die Zeit der Quelle. Danach wird überprüft, ob die gewünschte Zustandsänderung per update() übertragen wird. Da nicht vorauszusehen ist, wieviel Zeit zwischen Aufruf der Methode und Zustandsübertragung erfolgt, wird per guarded block auf das Ende des update() gewartet. QChessClockJavaAVTest: Die Testklasse implementiert die Interfaces QChessClockObservable und QChessClockInterface. Zu Beginn eines Tests wird eine neue GUI erstellt, die sich mit addObserver() bei der Testklasse registriert. Das zu testende GUI-Element wird auf dem Swing-EDT mit invokeAndWait(() -> doClick()) ausgelöst. Damit wird über actionPerformed() die entsprechende Methode in der Testklasse aufgerufen und eine entsprechende Zustandsänderung ausgeführt. Der EDT wird nicht verlassen, daher wartet invokeAndWait(), bis die dem Event zugeordnete Methode fertig ausgeführt ist. Das Testklassenobjekt ist damit in einem testbaren Zustand, und der entsprechende assert kann ausgeführt werden. Die andere Richtung, Uhr-Zustandsänderungen führen zu Änderung der Darstellung, wird bislang nur durch Änderungen des Status getestet. getestet. Hier wird der Zustand der Uhr gesetzt, der dann im TimerTask per notifyObservers() an die GUI übertragen wird. Der Test muss dann im guarded Block auf die Nachricht von notifyObservers warten, dass alle beauftragten GUI-Änderungen auf dem EDT liegen, und kann nach Ende des wait() die entsprechenden GUI-Eigenschaften abfragen. Lediglich der aboutTest() macht mir noch Kopfweh. Da warte ich per sleep() einen geschätzten Zeitraum darauf, dass ein JOptionPane aufgebaut wird, um den Button abfragen zu können. Gibt es da keine geschicktere Lösung? invokeAndWait() zum Aufruf des entsprechenden Menüpunkts geht nicht, weil der Aufruf solange wartet, bis der schließende Button gedrückt wird, und dann ist das Pane halt weg und kann nicht mehr untersucht werden. lg QNo
[toc] | [prev] | [next] | [standalone]
| From | "Christian H. Kuhn" <qno-news@qno.de> |
|---|---|
| Date | 2016-07-23 23:15 +0200 |
| Message-ID | <dvi553FjcemU1@mid.individual.net> |
| In reply to | #13032 |
Am 23.07.2016 um 22:37 schrieb Stefan Ram: > Dies gilt auch für den Aufbau der graphischen > Benutzeroberfläche (seit zirka 2006). Inzwischen habe ich dann auch entdeckt, dass https://docs.oracle.com/javase/tutorial/uiswing/concurrency/dispatch.html nicht alleine steht, sondern Teil eines Tutorials ist. Als erste Erkenntnis habe ich dann mal alle Aufrufe des Konstruktors der GUI per SwingUtilities.invoke...() erledigt. Mit weiteren Erkenntnissen ist zu rechnen ;-) lg QNo
[toc] | [prev] | [next] | [standalone]
Page 1 of 2 [1] 2 Next page →
Back to top | Article view | de.comp.lang.java
csiph-web