Permalänk

Hur gör man trådar i JavaFX

Med JavaFX så kan man skapa GUI appikationer i Java. Tack vare Scenebuilder så kan man bara klicka och dra det man har behov utav och sedan sätta funktionalitet på sina objekt.

Jag har två stycken funktioner som anropas när jag tycker på två olika knappar.

/* * Start measuring */ @FXML void start(ActionEvent event) { } /* * Stop measuring */ @FXML void stop(ActionEvent event) { }

Låt oss säga att jag har en klass som fungerar som en tråd. Hur ska jag få tråden att kunna lyda start() och stop() här?

Permalänk

För att använda wait och notify på en tråd från en annan tråd behöver du referenser till de trådar du vill stanna. om du vill stanna en annan tråd som håller på med något kan du sätta while(boolean) i run metoden för den tråden. sen ändra booleanen från någonannanstans så hoppar den ur while satsen och dör. vill du däremot köra den igen måste du skapa en ny tråd.

Visa signatur

9600k gtx 1080

Permalänk
Skrivet av professorsnö:

För att använda wait och notify på en tråd från en annan tråd behöver du referenser till de trådar du vill stanna. om du vill stanna en annan tråd som håller på med något kan du sätta while(boolean) i run metoden för den tråden. sen ändra booleanen från någonannanstans så hoppar den ur while satsen och dör. vill du däremot köra den igen måste du skapa en ny tråd.

så med andra ord,fixa en referens till klassen som har trådenn du vill stanna. ha en metod där som sätter en boolean till true eller false. nu i den tråden du vill stanna, i run metoden där sätter du while(boolean).

trycker du på start måste du ändra booleanen och skapa en nytt objekt av din klass och runna den igen.

Visa signatur

9600k gtx 1080

Permalänk
Skrivet av professorsnö:

För att använda wait och notify på en tråd från en annan tråd behöver du referenser till de trådar du vill stanna. om du vill stanna en annan tråd som håller på med något kan du sätta while(boolean) i run metoden för den tråden. sen ändra booleanen från någonannanstans så hoppar den ur while satsen och dör. vill du däremot köra den igen måste du skapa en ny tråd.

Så här menar du:

public class Measureing extends Thread{ private boolean active; public Measureing() { } @Override public void run() { while(true) { while(active) { // Do stuff here! } } } public boolean isActive() { return active; } public void setActive(boolean active) { this.active = active; } }

Där efter

@FXML void start(ActionEvent event) { measuring.setActive(true); } /* * Stop measuring */ @FXML void stop(ActionEvent event) { measuring.setActive(false); } ... measuring = new Measureing(); measuring.start();

Jag har hört att trådar i JavaFX är inte samma sak som i Java. I JavaFX så finns det något som heter "RunLater". Jag vet inte vad den gör.

Permalänk

sålänge din measuring referens pekar på rätt tråd så ska ditt sätt funka. runlater funkar typsåhär kolla. om du har en random tråd som ska göra ändringar i javafx ui't så blir det fel. vissa särskilda ändringar i javafx måste ske via en javafx tråd. Då skapar du en annonym klass i din kod som är runlater, då får du en runmetod som tillhör javafx, och där i kan du göra ändringar. sålänge du skapar en runlater i din kod och sedan kör den runlatern med en tråd kommer javafx tråden göra det som står innuti din runlater.

Visa signatur

9600k gtx 1080

Permalänk

@heretic16: när trycker på stopp försvinner din tråd. du kan inte ändra boolean till true igen för att den ska börja köra. när du trycker på start ska du göra new(ditt klassnamn som har tråden du vill starta).start(); och samtidigt ändra din boolean till true.

så för att summera. trycker du på stop ändras din while och tråden stannar. det betyder att din tråd dör.
vill du starta den tråden igen måste du skapa en nytt objekt av klassen.
så när du trycker på start vill:
new(KlassMedTråd).start();
ändra din boolean(true); -- detta så att tråden gör igång direkt och inte stannar.

för att stoppa
ändra endast boolean

Visa signatur

9600k gtx 1080

Permalänk

@professorsnö:

Jag förstår inte. Kan du visa med lite kod? Du kan låna min kod ovan

Permalänk

Vad tror du om detta förslag?

public class Measureing extends Thread{ private boolean active; static public boolean endThread; public Measureing() { } @Override public void run() { while(endThread == false) { System.out.println("Off"); while(active) { System.out.println("Hello!"); try { Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } try { Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } public boolean isActive() { return active; } public void setActive(boolean active) { this.active = active; } }

Den fungerar när man styr active och endThread utifrån JavaFX klasser Men Thread.sleep är nödvändigt. Annars fungerar det INTE!

Permalänk

https://gyazo.com/dfe4b8b21771bdd3fffc161e70996de8 detta är klassen med knapparna

https://gyazo.com/b4c6a30e2ffd71e486d3c14c8d28ac87 detta är klassen med tråden

detta ska fungera. trycker du på ena knappen kommer "writing" skrivas ut. stoppar du den slutar den göra det. trycker du på start igen kommer " writing" skrivas ut igen.,

om du inte får ditt till att funka. prova göra en AtomicBoolean som styr din tråd. på det sättet blir anropet trådsäkert, kan vara det som strular för dig.

private AtomicBoolean active = new AtomicBoolean();
med detta ändrar du värdet i din active med active.set metod.

Visa signatur

9600k gtx 1080

Permalänk

@heretic16: hade du kunnat flytta ut din andra while-loop ur din första? så du har en while-loop som skriver ut "off", sen en whileloop för active

Visa signatur

9600k gtx 1080

Permalänk

Du behöver endast javafx runlater tråden ifall du vill göra ändringar i en javafx komponent som ska visas eller visas. du kan nå javafx tråden från vilken tråd som helst i programmet genom att:

Platform.runLater(() -> {
try {
//an event with a button maybe
System.out.println("button is clicked");
} catch (IOException | COSVisitorException ex) {
Exceptions.printStackTrace(ex);
}
});

du kan göra en platform.runlater innuti run-metod för vilken tråd som helst. sen lägger du in den metoden som ska göra ändringar i javafx innuti runmetoden i platform.runlater

Visa signatur

9600k gtx 1080

Permalänk
Skrivet av professorsnö:

Du behöver endast javafx runlater tråden ifall du vill göra ändringar i en javafx komponent som ska visas eller visas. du kan nå javafx tråden från vilken tråd som helst i programmet genom att:

Platform.runLater(() -> {
try {
//an event with a button maybe
System.out.println("button is clicked");
} catch (IOException | COSVisitorException ex) {
Exceptions.printStackTrace(ex);
}
});

du kan göra en platform.runlater innuti run-metod för vilken tråd som helst. sen lägger du in den metoden som ska göra ändringar i javafx innuti runmetoden i platform.runlater

Varför fungerar inte mitt exempel då? Jag har testat den och den fungerar utmärkt. Då är det en tråd som ständigt körs hela tiden.
Det kanske är dålig praktik?

Edit:
Ok! Ditt förslag är bättre. Jag har testat det och skall använda mig av det. Men jag har en fråga till. Hur blir det när jag stänger av programmet, utan att anropa stop() funktionen? Borde inte tråden du automatiskt stängas utav?

I min IDE (Eclipse) så kan jag fortfarande se att tråden rullar på, trots att jag har stängt ned min GUI.

Permalänk

@heretic16: Du kan stänga av tråden i samband med ditt GUI genom att skapa en windowlistener:

JFrame frame = ...
frame.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {

}
});

i public void windowclosing skriver du vad du vill ska hända när rutan stängs. har för mig att alla trådar ska dö när du stänger av programmet. e dock osäker

edit:du kan även:
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

i ditt gui. kalla på framen. om ditt ui extendar jframe kallar du på this.setDefaultClose....

Glömde helt bort att detta endast gäller för Swing. Det finns samma metod för JavaFX. Sorry. Detta funkar för javafx.
namn-på-ditt-stage.setOnCloseRequest( event -> { det som ska hända;}

Visa signatur

9600k gtx 1080

Permalänk
Skrivet av professorsnö:

@heretic16: Du kan stänga av tråden i samband med ditt GUI genom att skapa en windowlistener:

JFrame frame = ...
frame.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {

}
});

i public void windowclosing skriver du vad du vill ska hända när rutan stängs. har för mig att alla trådar ska dö när du stänger av programmet. e dock osäker

edit:du kan även:
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

i ditt gui. kalla på framen. om ditt ui extendar jframe kallar du på this.setDefaultClose....

Glömde helt bort att detta endast gäller för Swing. Det finns samma metod för JavaFX. Sorry. Detta funkar för javafx.
namn-på-ditt-stage.setOnCloseRequest( event -> { det som ska hända;}

Så här här jag jag gjort i main-controller. Men problemed är att jag skulle gärna vilja få tag på objektet "Stage front" i kontrollern "front.fxml"

Använd [<code>] [</code>] utan <, >

import java.util.Optional; import javafx.application.Application; import javafx.application.Platform; import javafx.fxml.FXMLLoader; import javafx.scene.Parent; import javafx.scene.Scene; import javafx.scene.control.Alert; import javafx.scene.control.Alert.AlertType; import javafx.scene.control.ButtonType; import javafx.stage.Stage; public class Main extends Application{ /* * Start the start(Stage front) */ public static void main(String[] args) { launch(args); } @Override public void start(Stage front) throws Exception { front.setOnCloseRequest(e->{ e.consume(); askForClosing(); }); Parent root = FXMLLoader.load(getClass().getResource("front.fxml")); Scene scene = new Scene(root); front.setScene(scene); front.setTitle("JUBSPlotter"); front.show(); } private void askForClosing() { Alert question = new Alert(AlertType.CONFIRMATION); question.setTitle("Closing"); question.setHeaderText("Do you want to close?"); question.setResizable(false); question.setContentText("Press OK to close."); Optional<ButtonType> answer = question.showAndWait(); if(answer.get() == ButtonType.OK) { Platform.exit(); } } }

Permalänk

@heretic16: Yes jag förstår hur du menar. Kolla här. I din main klass gör du såhär:

public class Main extends Application { private static Stage stage; /* * Start the start(Stage front) */ public static void main(String[] args) { launch(args); } @Override public void start(Stage front) throws Exception { Main.stage = front; showFront(); } public static Stage frontStage() { return stage; } private static void showFront() { Parent root = FXMLLoader.load(getClass().getResource("front.fxml")); Scene scene = new Scene(root); front.setScene(scene); front.setTitle("JUBSPlotter"); front.show(); } private static void askForClosing() { Alert question = new Alert(AlertType.CONFIRMATION); question.setTitle("Closing"); question.setHeaderText("Do you want to close?"); question.setResizable(false); question.setContentText("Press OK to close."); Optional<ButtonType> answer = question.showAndWait(); if (answer.get() == ButtonType.OK) { Platform.exit(); } } }

Visa signatur

9600k gtx 1080

Permalänk

@heretic16: Sen i din controller får du implementera Initializable. Add unimplemented methods. då får du en initialize() metod. den metoden funkar som en "kontruktor" för rutan som öppnas. så fort rutan öppnas utförs det som står i din initialize(). så inuti initialize lägger du

Main.frontStage.setOnCloseRequest(e -> { e.consume(); Main.askForClosing(); });

Visa signatur

9600k gtx 1080

Permalänk

@heretic16: Jag vet inte huruvida detta är bad practice men jag har sett flertalet javafx tutorials som gör många metoder i main-klassen statiska. så det som kontrollerar själva uit kan vara statiska.

Visa signatur

9600k gtx 1080

Permalänk
Skrivet av professorsnö:

@heretic16: Jag vet inte huruvida detta är bad practice men jag har sett flertalet javafx tutorials som gör många metoder i main-klassen statiska. så det som kontrollerar själva uit kan vara statiska.

Nu förstår jag. Du har alltså en statisk metod som kan retunera tillbaka ett object av Stage.

Vad tror du om denna kod då? Då kan jag sätta active till false eller true lite överallt.
Java är fantastiskt!

public class Measureing extends Thread { static public boolean active; /// <-------- Statisk. private Plot plot; private Alert alert; public Measureing(Plot plot) { this.plot = plot; } @Override public void run() { active = true; while (active) { System.out.println("Hello!"); try { Thread.sleep(plot.getSampleTime().getSelectionModel().getSelectedItem()); } catch (Exception e) { alert.setTitle("Error!"); alert.setHeaderText("Missing sample time."); alert.setContentText("You need to set the sample time in Plot."); alert.showAndWait(); active = false; } } } }

Permalänk

@heretic16: Hahaha. Var försiktig med statiska grejer. Jag använder endast det i JavaFX Main klassen för att styra rutorna. För att få referenser till andra objekt ska du arbeta objektorienterat. Använd konstruktorerna i dina klasser för att skicka in referenser på de objekt du vill kunna nå.

Visa signatur

9600k gtx 1080

Permalänk
Hedersmedlem

Har inte kodat Java på länge men en statisk variabel på det viset kommer väl kontrollera alla trådar som skapas? Är det verkligen vad man vill?
Den borde väl också hanteras via en funktion och inte vara public direkt sådär?

Permalänk
Medlem

Du måste deklarera active som volatile för att det alltid ska fungera. Utan volatile är det inte säkert att ändringen av active i en tråd syns när du läser active i din andra tråd.
Trådarna kan tex köras på två helt olika kärnor i din processor som läser/skriver active till register eller närmsta cacheminne och ser därför bara sin egen lokala kopia men inte vad den andra tråden skriver.
Läsning/skrivning till ett volatile fält är en synchronization action och bildar en synchronization order mellan skrivningen och läsningen av samma fält. Enligt java specifikationen innebär det att läsningar av fältet måste kunna observera skrivningar som skett tidigare. Implementationsmässigt innebär det att man inte bara skriver till register/cache utan till huvudminnet.

Att skriva korrekta program med trådar och synkronisering är svårt. Om du inte har koll på javas minnesmodell och concurrency i java så kommer alla dina program vara buggiga.
Jag skulle rekommendera att du kollar in Aleksey Shipilëvs site https://shipilev.net/ Börja med Java Memory Model Pragmatics. Brian Göetz bok Java Concurrency in Practice är också bra även fast den är väldigt gammal (java 6).

Permalänk
Skrivet av professorsnö:

@heretic16: Hahaha. Var försiktig med statiska grejer. Jag använder endast det i JavaFX Main klassen för att styra rutorna. För att få referenser till andra objekt ska du arbeta objektorienterat. Använd konstruktorerna i dina klasser för att skicka in referenser på de objekt du vill kunna nå.

Hur ska jag annars komma åt fält från olika klasser till en klass? Annars måste jag importera dom.
Jag brukar i praktiken alltid köra statiska fält för att styra trådar. Blir så mycket enklare.

Skrivet av Shimonu:

Har inte kodat Java på länge men en statisk variabel på det viset kommer väl kontrollera alla trådar som skapas? Är det verkligen vad man vill?
Den borde väl också hanteras via en funktion och inte vara public direkt sådär?

Jag vill ha det så. Enkelt att stänga av en klass.
Tänk på att det är JavaFX jag kör.

Skrivet av jclr:

Du måste deklarera active som volatile för att det alltid ska fungera. Utan volatile är det inte säkert att ändringen av active i en tråd syns när du läser active i din andra tråd.
Trådarna kan tex köras på två helt olika kärnor i din processor som läser/skriver active till register eller närmsta cacheminne och ser därför bara sin egen lokala kopia men inte vad den andra tråden skriver.
Läsning/skrivning till ett volatile fält är en synchronization action och bildar en synchronization order mellan skrivningen och läsningen av samma fält. Enligt java specifikationen innebär det att läsningar av fältet måste kunna observera skrivningar som skett tidigare. Implementationsmässigt innebär det att man inte bara skriver till register/cache utan till huvudminnet.

Att skriva korrekta program med trådar och synkronisering är svårt. Om du inte har koll på javas minnesmodell och concurrency i java så kommer alla dina program vara buggiga.
Jag skulle rekommendera att du kollar in Aleksey Shipilëvs site https://shipilev.net/ Börja med Java Memory Model Pragmatics. Brian Göetz bok Java Concurrency in Practice är också bra även fast den är väldigt gammal (java 6).

Kan du visa ett exempel i JavaFX.

I detta fall har jag bara en tråd som körs med JavaFX. Jag gillar inte Java som är äldre än 11. Java 6 är ju riktigt Windows 98 för mig

Permalänk

när du skapar säg objektA och du vill att objektB ska känna till objektA. då får du skicka med A till B i B's konstruktor. sen får du ha setmetoder i objektA på de fält du vill ändra. sen kallar du på de metoderna från objektB via objektA referensen

Visa signatur

9600k gtx 1080

Permalänk
Hedersmedlem
Skrivet av heretic16:

Jag vill ha det så. Enkelt att stänga av en klass.
Tänk på att det är JavaFX jag kör.

Du kan inte stänga av en klass, bara alla instanser av en klass.
Tänk på att det är Java du kör

Det är ett väldigt ologiskt sätt att göra det och jag vet ju hur du gillar kod som är logisk och lättläst.

Så en liten statisk funktion som heter EndThreads/Deactivate eller vad du nu vill kalla det som satte active till false är väl logiskt och lättläst?

Permalänk
Medlem
Skrivet av heretic16:

Kan du visa ett exempel i JavaFX.

Det har inget med JavaFX att göra utan hur man säkert delar information mellan olika trådar i java. Alla kodexempel postade här med en while(active) loop innehåller samma fel. Du måste ändra public/private boolean active till public/private volatile boolean active för att programmen ska fungera korrekt.

Ett annat alternativ är att du använder Task/Service/ScheduledService från javafx.concurrent. Skrev ett kort exempel med ScheduledService. Ser koden lite konstig ut så är det för att den är skriven i scala men det är enkelt att direkt översätta till java.

class JfxApp extends Application { private val runLaterText = new Text("runLater") def start(stage: Stage): Unit = { val startButton = new Button("start") startButton.setOnAction(_ => service.start()) startButton.disableProperty().bind(service.stateProperty().isNotEqualTo(Worker.State.READY)) val stopButton = new Button("stop") stopButton.setOnAction { _ => service.cancel() service.reset() } stopButton.disableProperty().bind(service.stateProperty().isNotEqualTo(Worker.State.SCHEDULED)) val lastValueText = new Text() val resultString = new SimpleStringProperty("lastValue: ").concat(service.lastValueProperty()) // lastValueProperty uppdateras trådsäkert med senaste returvärdet från den Task ScheduledService kör lastValueText.textProperty().bind(resultString) val box = new VBox(10, startButton, stopButton, lastValueText, runLaterText) stage.setScene(new Scene(box, 200, 200)) stage.show() stage.setOnCloseRequest(_ => blocking.shutdown()) } private val service = new ScheduledService[Int] { def createTask(): Task[Int] = () => { // while/if (!isCancelled()) för att avbryta mitt i en task som tar lång tid att köra Platform.runLater(() => runLaterText.setText(s"runLater: ${Random.nextInt(100)}")) // trådsäkert sätt att uppdatera en jfx node genom att köra en Runnable på jfx tråden Random.nextInt(100) // returvärde uppdaterar lastValueProperty } } private val blocking = Executors.newCachedThreadPool() service.setPeriod(Duration.seconds(1)) // hur ofta ska createTask köras service.setExecutor(blocking) // var ska de Tasks som skapas köras def launch(): Unit = Application.launch() }

Skrivet av heretic16:

I detta fall har jag bara en tråd som körs med JavaFX. Jag gillar inte Java som är äldre än 11. Java 6 är ju riktigt Windows 98 för mig

Om du skapar en egen tråd i en JavaFX applikation så har du flera trådar, JavaFX Application Thread och din skapade tråd. Alla uppdateringar av grafiken måste göras i jfx tråden och alla events som genereras när du klickar på en knapp tex behandlas av jfx tråden. Alla beräkingar som tar tid eller annat som kan blockera en tråd måste göras i andra trådar. Därför kommer concurrency, synkronisering och trådsäkerhet in i bilden så fort du ska göra något mer avancerat. Java Memory Model och stor del av det som används för synkronisering mellan trådar i java.util.concurrent skrevs till java 5 och är fortfarande lika aktuellt idag även om man kör jdk 11/12.