Multithreading und awt.EventQueue

Status
Nicht offen für weitere Antworten.

SuperSeppel13

Bekanntes Mitglied
Hallo Leute,
Ich habe hier ein ernstahftes Problem mit ser awt.EventQueue:

Ich schreibe gerade an einem Programm, das zeigen soll, wie Linien und Kreise gerastert werden und zeichne daher sehr große "Pixel" und vor allem mit verstellbarer Geschwindigkeit.
Dies kann jedoch lästig werden, wenn man beispielsweise ein gefülltes Rechteck zeichnet und versehentlich eine sehr langsame Zeichengeschwindigkeit gewählt hat.
Daher wollte ich zumindest eine Möglichkeit schaffen, den Zeichenvorgang abzubrechen, was jedoch einen Schritt ins (wie ich schon zu spüren bekommen habe) sehr komplexe Feld des Multithreading unabdingbar macht.
Ich habe also einen Thread eingerichtet, der das Zeichnen neuer
Figuren übernimmt.

Dazu habe ich in meine Klasse "DrawingPanel" (von JPanel abgeleitet, zum Zeichnen aller Figuren bestimmt) schlicht "Runnable" implementiert.
Dort werden dann aus der "run"-Methode heraus meine (selbstgeschriebenen) Zeichenmethoden aufgerufen.

Dies scheint auch wunderbar zu funtkionieren, mit dem einen Haken, dass die Wartezeit nach dem setzen jedes Pixels (von der ja die Zeichengeschwindigkeit abhängt) völlig zu entfallen scheint und die Figur immer sofort vollständig zu sehen ist.
Nach einiger Recherche fand ich erstaunt heraus, dass mein eigens angelegter Thread den Zeichenvorgang völlig korrekt ausführt, sich aber die EventQueue einmischt und alles zunichte macht:
Mein Thread (heiße er t) beginnt völlig korrekt mit der Ausführung der Zeichenmethode, setzt auch den ersten Pixel und beginnt dann zuwarten.
Nun mischt sich nach kurzer (sehr kurzer!) Zeit die EventQueue ein und beginnt ebenfalls (in einem weiteren Thread), die Zeichenmethode auszuführen (die "run" Methode wird nicht erneut ausgeführt), verzichtet aber großzügig aufs warten und setzt die Zeichenschleife fort, wodurch die Figur sofort erscheint. :bahnhof:
t zeichnet danach zwar noch völlig korrekt die Linie weiter (mit Warten), dies hat jedoch keinen Effekt mehr, das sie ja schon zur Gänze sichtbar ist. :x

An dieser stelle ist zu erwähnen, dass ich das Warten nicht mit Thread.sleep veranlasse (diese Methode rundet die eingegebene wartezeit in millisekunden auf hundertstelsekunden, wodurch sie für meine zwecke viel zu ungenau wird), sondern mit einer Leerschleife, die nach einer bestimmten zeit in nanosekunden beendet wird.
Ich halte den Thread also bloß beschäftigt, bis er weiterzeichnen soll. (Gibt's da bessere Lösungen?)

Wenn ich die Zeichenmethode mit "synchronized" synchronisiere, führt t das Zeichnen richtig aus und ich sehe die Figur langsam entstehen, kann währenddessen aber wiederum nicht auf meine steuerelemente zugreifen (warum eigentlich?). Außerdem mischt sich auch hier wieder die EventQueue ein, die nun zwar nicht mehr meinem t dazwischenfunken kann, trotzdem aber noch seinen Senf dazugeben muss und die Linie nochmal zeichnet - wieder ohne Warten. (auch wenn mich das an dieser stelle eigentlich nicht mehr zu interessieren braucht, finde ich es sonderbar)

Wie kommt die überhaupt dazu, sich da einzumischen?! Ich hab schon versucht sie einfach auszuschalten, finde aber keine Möglichkeit und halte es auch eigentlich nicht für eine gute idee, da ich nicht genau weiß, wozu die sonst noch gut ist.
Gibt es da irgendeine elegante lösung?
Bin echt verzweifelt.

Gute Nacht und vielen Dank für jegliche Hilfe,
SuperSeppel13
 

Marco13

Top Contributor
Was "Einmischen" heißt, ist nicht klar. Wenn du in der paint-Methode irgendeinen Thread startest oder sonstigen Murks, funktioniert das natürlich nicht. Apropos "Murks": Was meinst du mit "selbstgeschriebener Zeichenmethode"?
 

Ebenius

Top Contributor
Au wei. Die EventQueue ausschalten... Ohne den Event Dispatcher Thread funktioniert in AWT & Swing nix. Den kann man nich mal eben ausschalten. Gezeichnet wird immer in paintComponent und beinahe nie mit getGraphics(). Threads lässt man nicht per Schleife herumspazieren bis die Zeit stimmt. Für sowas gibt's eine javax.swing.Timer-Klasse. Wenn die nicht genau genug ist, dann ist System.currentTimeMillis auch nicht genau genug.

Erstmal hier lesen: Zeichnen in Swing Tutorial

Ebenius
 

Marco13

Top Contributor
Hab da gerade mal was zusammengestümpert. Für jede Linie ein neuer Thread ist zwar Bogus, aber ... hat einen Grund. Man kann mit dem einen Button das Zeichnen von Linien veranlassen, und mit dem anderen aktuelle Zeichenvorgände abbrechen.
Code:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.util.*;

class SlowGraphics
{
    private JComponent owner;
    private Color color;
    private int pixels[][];
    private int delay = 50;
    private boolean cancelled = false;

    public SlowGraphics(JComponent owner, int pixels[][])
    {
        this.owner = owner;
        this.pixels = pixels;
    }

    public void setColor(Color color)
    {
        this.color = color;
    }

    public void drawLine(final int x0, final int y0, final int x1, final int y1)
    {
        cancelled = false;
        final Color currentColor = color;
        Thread t = new Thread(new Runnable()
        {
            public void run()
            {
                int steps = 100;
                float x = x0;
                float y = y0;
                float dx = (float)(x1-x0)/steps;
                float dy = (float)(y1-y0)/steps;
                for (int i=0; i<100; i++)
                {
                    pixels[(int)x][(int)y] = currentColor.getRGB();
                    try
                    {
                        Thread.sleep(delay);
                    }
                    catch (InterruptedException e)
                    {
                        e.printStackTrace();
                    }
                    owner.repaint();
                    if (cancelled)
                    {
                        return;
                    }
                    x+=dx;
                    y+=dy;
                }

            }
        });
        t.start();
    }

    public void cancel()
    {
        cancelled = true;
    }

}


class SlowPaintPanel extends JPanel
{
    private int w;
    private int h;
    private int pixels[][];
    private SlowGraphics slowGraphics;

    public SlowPaintPanel(int w, int h)
    {
        this.w = w;
        this.h = h;
        pixels = new int[w][h];
        slowGraphics = new SlowGraphics(this, pixels);
    }

    public SlowGraphics getSlowGraphics()
    {
        return slowGraphics;
    }

    public void paintComponent(Graphics g)
    {
        super.paintComponent(g);
        int dx = getWidth() / w;
        int dy = getHeight() / h;
        for (int x=0; x<w; x++)
        {
            for (int y=0; y<h; y++)
            {
                g.setColor(new Color(pixels[x][y]));
                g.fillRect(x*dx,y*dy,dx,dy);
            }
        }
    }
}


class SlowPaintTest extends JFrame
{
    public static void main(String args[])
    {
        SwingUtilities.invokeLater(new Runnable()
        {
            public void run()
            {
                new SlowPaintTest().setVisible(true);
            }
        });
    }

    public SlowPaintTest()
    {
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setSize(800,800);

        getContentPane().setLayout(new BorderLayout());

        final int w = 50;
        final int h = 50;
        final SlowPaintPanel s = new SlowPaintPanel(w,h);
        getContentPane().add(s, BorderLayout.CENTER);

        final Random random = new Random(0);

        JButton b = new JButton("Draw random line");
        b.addActionListener(new ActionListener()
        {
            public void actionPerformed(ActionEvent e)
            {
                int r = random.nextInt(255);
                int g = random.nextInt(255);
                int b = random.nextInt(255);
                int x0 = random.nextInt(w);
                int y0 = random.nextInt(h);
                int x1 = random.nextInt(w);
                int y1 = random.nextInt(h);

                s.getSlowGraphics().setColor(new Color(r,g,b));
                s.getSlowGraphics().drawLine(x0,y0,x1,y1);
            }
        });
        getContentPane().add(b, BorderLayout.NORTH);

        b = new JButton("Cancel current lines");
        b.addActionListener(new ActionListener()
        {
            public void actionPerformed(ActionEvent e)
            {
                s.getSlowGraphics().cancel();
            }
        });
        getContentPane().add(b, BorderLayout.SOUTH);



    }



}
 

SuperSeppel13

Bekanntes Mitglied
Dickes Dankeschön dafür!
Ich glaube, damit kann ich was anfangen. Ist jetzt aber zu spät, das noch auszuprobieren.

Zu den Fragen:

- Habe das Tutorial schon gelesen und weiß, wie vorsichtig man mit getGraphics umgehen muss.
Fast alles zeichnen übernimmt auch paintComponent, aber wirklich alles darüber erledigen zu lassen wäre in meinem Fall extrem aufwenig und ginge stark auf Kosten der Übersichtlichkeit des Programms.
- Das mit dem Abschalten der EventQueue war nicht ganz ernst gemeint.
- Danke für den Hinweis mit dem Swing-Timer - werds ausprobieren.
- bei meiner slebstgeschriebenen Zeichenmethode handelt es sich um eine Implementierung des bekannten Bresenham-Algorithmus zum Rastern von Linien. Sie ist damit mit der java-eigenen drawLine-Methode fast identisch, soll jedoch in dem Stil zeichnen, wie das tolle Beispiel von Marco13.
- den Thread starte ich nicht aus der paint-Methode heraus, sondern als Folge eines Mausereignisses, welches das erstmalige Zeichnen einer neuen Linie auslöst.
- mit "Einmischen" meine ich, dass plötzlich ein Thread, den ich nicht manuell aufgerufen habe und der laut Konsolenausgabe mit der EventQueue in Verbindung zu bringen ist, mit der Ausführung meiner Zeichenmethode beginnt, noch wärend mein Zeichenthread mit deren Ausführung beschäftigt ist.

Ich bin mittlerweile darauf gekommen, dass besagter Thread aus der EventQueue bei jedem repaint aufgerufen wird und dass er wohl auch im beschriebenen Fall meine Zeichenmethode aufruft, um genau dieser Pflicht nachzukommen.

Damit stellt sich mir jetzt noch dringlicher die Frage, weshalb ich während des Zeichnens (das ja korrekt ausgeführt wird, wenn ich die methode als "synchronized" deklariere) nicht auf Steuerelemente zugreifen kann, obwohl mein main-Thread definitiv nicht ins Zeichnen involviert ist.
Kann es sein, dass die EventQueue den main-Thread blockiert, während sie einen repaint ausführt?

Auf jeden Fall danke für die schnellen Antworten und entschuldigt meine unzulängliche Problembeschreibung.

SuperSeppel13
 

Ebenius

Top Contributor
SuperSeppel13 hat gesagt.:
Kann es sein, dass die EventQueue den main-Thread blockiert, während sie einen repaint ausführt?
Kann es sein, dass alle Events (Mouse, Key, Repaint, Action, ...) auf dem selben Thread laufen? Nämlich dem Event Dispatch Thread... Wenn Du den blockierst, dann geht nix mehr zu klicken. Vergrößere doch mal während dessen das Fenster, oder leg ein anderes drüber und nimm's wieder weg, oder so. Alles der selbe Thread.

Nimm den javax.swing.Timer... Der schickt Dir in Intervallen ActionEvents über den EDT. Dann ist keine Synchronisierung nötig und nix wird langsam. :)

Happy Hacking!
Ebenius
 

SuperSeppel13

Bekanntes Mitglied
Habs jetzt wie in dem Beispiel mit eigenem Thread für jede Linie gemacht. Der swing.Timer ist leider genau so ungenau wie Thread.sleep, weshalb ich die Zeit mit System.nanoTime abrufe.
Auf jeden Fall funktioniert's jetzt.
Nochmal Danke,
SuperSeppel13
 

Marco13

Top Contributor
Das kommt davon :oops: Das war der einzige Punkt, den ich an obigem Beispiel explizit als "nicht so schön" herausgestellt hatte (Für jede Linie ein neuer Thread ist zwar Bogus,...) - aber man muss eben damit rechnen, dass alles, was man postet, gedankenlos und ohne Nach- und Hinterfragen übernommen wird.

Also nochmal: Das mit dem eigenen Thread pro Zeichenoperation ist nicht schön. Wenn d jettzt sowas machen würdest wie
Code:
for (int i=0; i<10000; i++) sg.drawLine(...);
hättest du mit einem Schlag 10000 Threads, und das ganze Programm würde sich mit einem leisen Wimmern (und/oder einem lauten Knall) verabschieden.

Eine "bessere" Lösung wäre auch etwas aufwändiger, aber ... kommt eben auch drauf an, welchen Anspruch du an dich und dein Programm hast.

Ein möglicher Ansatz, der aber immernoch spontan und unüberlegt ist, den du aber auf Anwendbarkeit für deinen Fall prüfen könntest, wäre im Psedocode
Code:
interface PaintOperation
{
    void execute();
    void cancel();
}

class LinePaintOperation implements PaintOperation
{
    private boolean cancelled = false;

    // Konstruktor mit x0,y0,x1,y1 usw...
    ...

    void execute()
    {
        // Wie die "run" im Beispiel oben: Setze die Pixel der Linie
    }
}


class SlowGraphics
{
    List<PaintOperation> paintOperations = ...

    void drawLine(int x0, int y0, int x1, int y1)
    {
         paintOperations.add(new LinePaintOperation(....));
    }

    // Diese Methode wird ständig von EINEM immer laufenden Thread ausgeführt
    private void work()
    {
        while(true)
        {
            // Achtung: SEHR Pseudocodig!!!
            while (!paintOperations.isEmpty())
            {
                paintOperations.get(0).execute();
                paintOperations.remove(0);
            }
        }
    }

    void cancel()
    {
        // Achtung: SEHR Pseudocodig!!!
        paintOperations.get(0).cancel();
        paintOperations.clear();
    }
}

Die Liste "paintOperations" könnte/sollte dabei eine BlockingQueue sein, und man muss halt auf die richtige Synchronisation und so achten - aber so von der Idee her:
Es gibt EINEN Thread, der IMMER läuft, und ALLE "langsamen" Zeichenoperationen nacheinander ausführt.
 
Status
Nicht offen für weitere Antworten.
Ähnliche Java Themen
  Titel Forum Antworten Datum
A Multithreading with SwingWorkers in ExecutorService AWT, Swing, JavaFX & SWT 1
Maxim6394 Multithreading in SWT AWT, Swing, JavaFX & SWT 1
H Multithreading & Swing AWT, Swing, JavaFX & SWT 7
DerEisteeTrinker Swing Swing ist zu schnell für Multithreading AWT, Swing, JavaFX & SWT 18
A Swing Swing + Multithreading :: IllegalMonitorStateException AWT, Swing, JavaFX & SWT 4
G zeitgesteuertes Multithreading für ProgressMonitor AWT, Swing, JavaFX & SWT 6
A Wie folgendes am besten realisieren (Multithreading.) AWT, Swing, JavaFX & SWT 16
M Problem mit System.setOut()/setErr() und MultiThreading AWT, Swing, JavaFX & SWT 11
J "Exception in thread "AWT-EventQueue-0"" Fehler AWT, Swing, JavaFX & SWT 3
A Swing Exception in thread "AWT-EventQueue-0" AWT, Swing, JavaFX & SWT 1
K Swing AWT-EventQueue-1 java.lang.NoClassDefFoundError bei setVisible(true) AWT, Swing, JavaFX & SWT 3
S AWT-EventQueue-0 NullPointerExeption bei drawLine AWT, Swing, JavaFX & SWT 5
VfL_Freak AWT Exception in der Eventqueue AWT, Swing, JavaFX & SWT 8
X Exception in thread "AWT-EventQueue-0" java.lang.ArrayIndexOutOfBoundsException: -1 AWT, Swing, JavaFX & SWT 6
Q "AWT-EventQueue-0" Exception Problem AWT, Swing, JavaFX & SWT 4
S Frage zu java.awt.EventQueue AWT, Swing, JavaFX & SWT 1
A Swing JOptionPane.showConfirmDialog, EventQueue wird weiter abgearbeitet AWT, Swing, JavaFX & SWT 2
L exception in thread awt-eventqueue-0 java.lang.nullpointerexception AWT, Swing, JavaFX & SWT 2
S Swing Exception in thread "AWT-EventQueue-0" bei Jlabel AWT, Swing, JavaFX & SWT 4
C Event Handling Exception in thread "AWT-EventQueue-0" java.lang.ClassCastException AWT, Swing, JavaFX & SWT 43
R Swing JFrame in der EventQueue oder nicht? AWT, Swing, JavaFX & SWT 6
O Immer Exception in AWT-EventQueue-0 AWT, Swing, JavaFX & SWT 5
F Exception in thread "AWT-EventQueue-0" java.lang.NumberFormatException: null AWT, Swing, JavaFX & SWT 5
A AWT Methodenaufruf "AWT-EventQueue-0" java.lang.NullPointerException AWT, Swing, JavaFX & SWT 4
S Exception in thread "AWT-EventQueue-0" java.lang.NullPointerException AWT, Swing, JavaFX & SWT 7
Lony AbstractTableModel Exception in thread "AWT-EventQueue- AWT, Swing, JavaFX & SWT 3
A Exception in thread "AWT-EventQueue-0" java.lang.N AWT, Swing, JavaFX & SWT 4
M MouseEvent in der EventQueue vor versetzen AWT, Swing, JavaFX & SWT 4

Ähnliche Java Themen

Neue Themen


Oben