Timer stoppen funktioniert nicht immer

Hallo,

ich habe eine Frage an die Profis hier im Forum, es geht um den Timer in Java.
Ich habe ein kleines Programm geschrieben, was nach dem drücken eines Buttons einfach abläuft und zwar so lange bis man es mit einem Mausklick unterbricht.
Das funktioniert alles wunderbar, um die abläufe im Programm zeitlich zu steuern, habe ich das erste mal einen Timer verwendet, auch das funktioniert wunderbar. Mit Thread.sleep hatte ich halt das Problem, das wärend Thread.sleep abläuft, das Programm nicht auf inputs wie z.B. von meiner PC-Maus reagiert, deshalb verwende ich den Timer.

Jetzt habe ich aber folgendes Problem.
Beim Timer kann man ja eine Zeit in ms einstellen, wann er den task mit seiner run - Methode aufrufen soll. Das funktioniert alles wunderbar, wenn die Zeit vom Timer z.B. 1000ms beträgt. Sobald ich aber die Zeit vom Timer heruntersetze z.B. auf 10ms dann reagiert das Programm nicht sofort auf den Mausklick und das Programm unterbricht nicht mehr sofot. Ich muss die Maus mehrmals hintereinander klicken, damit das Programm stopt.

Warum funktioniert das mit 1000ms wunderbar und bei 10ms fast garnicht mehr?

Hier der Teil vom Code mit dem Timer:

@Override
public void actionPerformed(ActionEvent e) {
    if(e.getSource() == move) { // JButton move

          timer = new Timer(); // Timer als globale Variable
          TimerTask task = new TimerTask(){
                                                               
                    @Override
                    public void run(){

                    // Hier ist der Code der nach einer Zeit vom Timer aufgerufen wird.
                    // Hier wird nur eine Zufallszahl generiert und dann als Position
                    // an rob.mouseMove übergeben.
                        
                    rob.mouseMove(randomNumberX, randomNumberY);
                    }
                };
                timer.schedule(task, 10, 10);   
            }   
        }
    
    @Override
        public void mouseClicked(MouseEvent e) {
            
            if(SwingUtilities.isRightMouseButton(e)) {
                timer.cancel();
                System.out.println("Stop");
            }   
        }

Viele Grüße

Es gibt da 1000 mögliche Gründe für ~„unerwartetes Verhalten“, und man müßte viele Fragen stellen und gegebenenfalls ein kleines Beispiel basteln wo man das selbst ausprobiert. (Robot ist halt auch ein ziemlich ‚harter Eingriff‘ ins System, da müßte man ggf. genauer hinschauen).

Aber… nur vom Drüberlesen: Dort steht
timer.schedule(task, 10, 10);
was sehr suspekt aussieht: Das bedeutet: Führe diesen Task (nach 10ms) alle 10ms aus. Ich nehme an, das sollte nur einmal gemacht werden?

Das soll sich so lange wiederholen, bis ich die rechte Maustaste drücke. Erst dann soll er das Programm beenden. Das ist so gewollt. Es funktioniert so auch schon wunderbar, halt nur nicht mit sehr geringen Zeiten wie. z.B. 10 ms oder 100ms. Bei 1000ms funktioniert es am einwandfrei.

Alle/Jede 10ms ist für Robot-technische Vorgänge zu knapp bemessen. Es kommt dabei zu Überschneidungen, die bestimmt unerwünscht sind. Nimm 250ms. (Du kannst dich auch schrittweise an die untere Grenze herantasteten… oder die Zeiten messen).

100 ms Intervalle sollten für den Spaß gerade so ausreichend sein…

import java.awt.*;
import java.util.Timer;
import java.util.TimerTask;

public class Maus {
    private double direction;
    private double speed;
    private Point lastXY;
    private Timer timer;

    public Point nextPoint() {
        int newX = (int) (lastXY.getX() + speed * Math.cos(direction));
        int newY = (int) (lastXY.getY() + speed * Math.sin(direction));
        return new Point(newX, newY);
    }

    public void starten() throws AWTException {
        direction = Math.PI * 2 * Math.random();
        speed = 10 + 20 * Math.random();
        lastXY = MouseInfo.getPointerInfo()
                .getLocation();
        Robot robot = new Robot();
        timer = new Timer();
        timer.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                Point newPoint = MouseInfo.getPointerInfo()
                        .getLocation();
                double distance = newPoint.distance(lastXY);
                if (distance < 10) {
                    Point nextPoint = nextPoint();
                    robot.mouseMove(nextPoint.x, nextPoint.y);
                    lastXY = nextPoint;
                } else {
                    stoppen();
                }
            }
        }, 100, 100);
    }

    public void stoppen() {
        timer.cancel();
        timer.purge();
        timer = null;
    }

    public static void main(final String[] args) throws AWTException {
        Maus maus = new Maus();
        maus.starten();
    }
}

Moin Moin :coffee:

Hier noch mit Fehlerbehandlung sowie Thread-safe…

import java.awt.*;
import java.util.Timer;
import java.util.TimerTask;

public class Maus {
    private double direction;
    private double speed;
    private Point lastXY;
    private Timer timer;

    public Point nextPoint() {
        int newX = (int) (lastXY.getX() + speed * Math.cos(direction));
        int newY = (int) (lastXY.getY() + speed * Math.sin(direction));
        return new Point(newX, newY);
    }

    public synchronized void starten() throws AWTException {
        if (timer != null) {
            return;
        }
        direction = Math.PI * 2 * Math.random();
        speed = 10 + 20 * Math.random();
        lastXY = MouseInfo.getPointerInfo()
                .getLocation();
        Robot robot = new Robot();
        timer = new Timer();
        timer.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                Point newPoint = MouseInfo.getPointerInfo()
                        .getLocation();
                double distance = newPoint.distance(lastXY);
                if (distance < 10) {
                    Point nextPoint = nextPoint();
                    robot.mouseMove(nextPoint.x, nextPoint.y);
                    lastXY = nextPoint;
                } else {
                    stoppen();
                }
            }
        }, 100, 100);
    }

    public synchronized void stoppen() {
        if (timer == null) {
            return;
        }
        timer.cancel();
        timer.purge();
        timer = null;
    }

    public static void main(final String[] args) throws AWTException {
        // Tests:
        Maus maus = new Maus();
        for (int i = 0; i <= 10_000; i++) {
            new Thread(() -> {
                try {
                    Thread.sleep((long) (1000 * Math.random()));
                    maus.starten();
                } catch (final Exception e) {
                    throw new RuntimeException(e);
                }
            }).start();
        }
        for (int i = 0; i <= 100; i++) {
            new Thread(() -> {
                try {
                    Thread.sleep((long) (100 * Math.random()));
                    maus.stoppen();
                } catch (final Exception e) {
                    throw new RuntimeException(e);
                }
            }).start();
        }
    }
}

Jetzt kann vereinfacht gesagt nix mehr schiefgehen… Keine Locks, keine race conditions, kein data loss.

Die Tests simulieren den beinahe gleichzeitigen Zugriff von 10 000 Usern (in 1s), was in der Realität meist eh nur bei Banken vorkommt.

Hoffe, das Beispiel hilft.

Danke für die Hilfe!

1 „Gefällt mir“

Gestern habe ich mein Programm fertiggestellt.
Was mir dann aufgefallen ist, ist sehr interessant.
Starte ich mein Programm in Eclipse, dann bewegt sich die Maus viel schneller, als im exportiereten Programm was ich dann am Desktop ausführe.
Das Programm funktioniert trotzdem sehr gut, aber warum ist das so?

Ggf. hat Eclipse ein eigenes, spezielles (und schnelleres…) JRE. Glaube, @Marco13 weiß da mehr.