我是JavaFx/Concurrency的新手,所以我閱讀了Concurrency in JavaFX的教程,但是我仍對JavaFX Gui中後臺線程的實現感到困惑。JavaFX併發和任務(在Task中運行的線程)
我正在嘗試編寫一個與一些串行設備(使用JSSC-2.8)接口的小GUI,並根據這些設備的響應更新GUI。但是,在寫入消息的時間和設備響應時間之間存在滯後,並且使用Thread.sleep()達到任意長度的時間對我來說不是一個可靠的方法。因此,我想使用併發包中的wait()和notify()方法(使用所有適當的同步),但我不確定如何實現它。我最初做的是在任務內創建另一個線程,它將寫入消息並等待響應,並使用一些綁定來更新GUI。我在最後包含了我的代碼。這裏是僞代碼我想實現的一個短表:
start Task:
connect to serial devices
synchronized loop:
send messages
wait() for event to fire
notify()
但是,什麼是被髮生的事情是,當我調用wait(),整個應用程序空轉,然後當通知()被調用(在響應觸發和事件之後),它不會繼續在recipe()循環或startTdk()循環中停留的地方,它只是空閒。我是否實現了線程錯誤?當我調用wait()時,是否有可能導致EventDispatch或JavaFX應用程序線程暫停?
我希望問題很明確,如果有任何需要澄清的地方,我可以更新帖子。
public class OmicronRecipe extends Service<String> implements Runnable{
private final String SEPERATOR=";";
private final Tdk tdk;
private final Pvci pvci;
private final SimpleStringProperty data = new SimpleStringProperty("");
private final Float MAX_V = 26.0f,UHV=1e-8f;
private boolean isTdkOn=false, isPvciOn=false;
private String power;
private Float temp,press,maxT, setT;
private int diffMaxT,diffP,diffPow, diffT, index=0;
public OmicronRecipe(){
tdk = new Tdk("COM4");
pvci = new Pvci("COM5");
}
private synchronized void recipe(){
while (true){
try {
sendMessages();
data.set(power+SEPERATOR+temp+SEPERATOR+press);
calcDiffs();
if (diffPow < 0){
if(diffMaxT < 0){
if(diffT < 0){
if (diffP < 0){
if(!rampPow()){
//Max Power reached
}
}else{
//Wait for pressure drop
}
}
}else{
//Wait until quit
}
}else{
//Max power reached
}
Thread.sleep(5000);
} catch (InterruptedException ex) {
Logger.getLogger(OmicronRecipe.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
private synchronized boolean rampPow(){
boolean isRamped=false;
Float setPow = tdk.getSetPow(index), curPow;
setT = tdk.getSetT(index);
curPow = Float.parseFloat(power);
if(curPow.compareTo(setPow) < 0){
do{
curPow += 0.1f;
tdk.sendMessage("PV "+curPow+"\r");
try {
wait();
} catch (InterruptedException ex) {
Logger.getLogger(OmicronRecipe.class.getName()).log(Level.SEVERE, null, ex);
}
curPow = Float.parseFloat(power);
}while(curPow.compareTo(setPow) < 0);
index++;
isRamped=true;
}
return isRamped;
}
public synchronized boolean connect(){
if(!isTdkOn && !isPvciOn){
isTdkOn = tdk.connect();
isPvciOn = pvci.connect();
}
return isTdkOn && isPvciOn;
}
public synchronized boolean disconnect(){
if(tdk!=null && pvci !=null){
isTdkOn = tdk.disconnect();
isPvciOn = pvci.disconnect();
}
return !isTdkOn && !isPvciOn;
}
public synchronized StringProperty getData(){
return data;
}
public void setMaxT(Float maxT){
this.maxT = maxT;
}
private synchronized void calcDiffs(){
Float pow = Float.parseFloat(power);
diffPow = pow.compareTo(MAX_V);
diffMaxT = temp.compareTo(maxT);
diffT = temp.compareTo(100f);
diffP = press.compareTo(UHV);
}
private synchronized void setListeners(){
tdk.getLine().addListener((ov,t, t1)-> {
synchronized (this){
System.out.println("New Power: "+t1);
power = t1;
this.notify();
}
});
pvci.getLine().addListener((ov,t,t1) ->{
synchronized (this){
String[] msg = t1.split(SEPERATOR);
if(msg.length == 2){
switch(msg[0]){
case "temperature":
System.out.println("Temperaute");
temp = Float.parseFloat(msg[1]);
break;
case "pressure":
System.out.println("Pressure");
press = Float.parseFloat(msg[1]);
break;
default:
System.out.println("Nothing; Something went wrong");
break;
}
}
this.notify();
}
});
}
private synchronized void sendMessages(){
try {
tdk.sendMessage("PV?\r");
this.wait();
pvci.sendMessage("temperature");
this.wait();
pvci.sendMessage("pressure");
this.wait();
} catch (InterruptedException ex) {
Logger.getLogger(OmicronRecipe.class.getName()).log(Level.SEVERE, null, ex);
}
}
private synchronized boolean startTdk(){
boolean isOut=false;
if(isTdkOn){
try {
tdk.sendMessage("ADR 06\r");
this.wait();
System.out.println("Power: "+power);
if(power.equals("OK")){
tdk.sendMessage("OUT?\r");
this.wait();
if(power.equals("OFF")){
tdk.sendMessage("OUT ON\r");
this.wait();
isOut = power.equals("ON");
}
else{
isOut = power.equals("ON");
}
}
} catch (InterruptedException ex) {
Logger.getLogger(OmicronRecipe.class.getName()).log(Level.SEVERE, null, ex);
}
}
return isOut;
}
@Override
protected Task<String> createTask() {
return new Task<String>() {
@Override
protected String call() throws IOException{
new Thread(new OmicronRecipe()).start();
return "";
}
};
}
@Override
public void run() {
if (connect()){
setListeners();
if(startTdk()){
recipe();
}
}
}
}
我不會包含Pvci類,因爲它只是Tdk類的一個副本,但具有與該機器交談的特定消息序列。
public class Tdk {
private SerialPort tdkPort;
private final String portName;
private StringBuilder sb = new StringBuilder("");;
private final StringProperty line = new SimpleStringProperty("");
private final HashMap<Float,Float> calibMap;
private ArrayList<Float> list ;
private boolean isEnd=false;
public Tdk(String portName){
this.portName = portName;
System.out.println("TDK at "+portName);
calibMap = new HashMap();
setMap();
}
public synchronized boolean connect(){
tdkPort = new SerialPort(portName);
try {
System.out.println("Connecting");
tdkPort.openPort();
tdkPort.setParams(9600,
SerialPort.DATABITS_8,
SerialPort.STOPBITS_1,
SerialPort.PARITY_NONE);
tdkPort.setEventsMask(SerialPort.MASK_RXCHAR);
tdkPort.addEventListener(event -> {
if(event.isRXCHAR()){
if(event.getPortName().equals(portName)){
try {
if(!isEnd){
int[] str = tdkPort.readIntArray();
if(str!=null)
hexToString(str);
}
if(isEnd){
System.out.println("Here: "+sb.toString());
isEnd=false;
String d = sb.toString();
sb = new StringBuilder("");
line.setValue(d);
}
} catch (SerialPortException e) {
Logger.getLogger(Tdk.class.getName()).log(Level.SEVERE, null, e);
}
}
}
});
} catch (SerialPortException e) {
Logger.getLogger(Tdk.class.getName()).log(Level.SEVERE, null, e);
}
return tdkPort !=null && tdkPort.isOpened();
}
public synchronized boolean disconnect(){
if(tdkPort!=null) {
try {
tdkPort.removeEventListener();
if (tdkPort.isOpened())
tdkPort.closePort();
} catch (SerialPortException e) {
Logger.getLogger(Tdk.class.getName()).log(Level.SEVERE, null, e);
}
System.out.println("Disconnecting");
}
return tdkPort.isOpened();
}
public synchronized void sendMessage(String message){
try {
tdkPort.writeBytes(message.getBytes());
} catch (SerialPortException e) {
Logger.getLogger(Tdk.class.getName()).log(Level.SEVERE, null, e);
}
}
private void setMap(){
calibMap.put(1.0f, 25.0f);
calibMap.put(7.0f, 125.0f);
calibMap.put(9.8f, 220.0f);
list = new ArrayList(calibMap.keySet());
}
public Float getSetPow(int index){
return list.get(index);
}
public Float getSetT(int index){
return calibMap.get(list.get(index));
}
public synchronized StringProperty getLine(){
return line;
}
private synchronized void hexToString(int[] hexVal){
for(int i : hexVal){
if(i != 13){
sb.append((char)i);
}else{
isEnd=true;
}
}
System.out.println("Turning: "+Arrays.toString(hexVal)+" to String: "+sb.toString()+" End: "+isEnd);
}
我的意思是空閒的應用程序,而不是凍結它,我的錯誤。我更新了我的帖子。但我會更深入地瞭解DataBinding,以瞭解如何實現它。謝謝 – artyfrost
綁定可能不會像這裏所描述的那樣工作。嘗試更新由FXAT以外的其他任何GUI元素觀察到的對象將導致運行時錯誤。所以你仍然需要更新RunLater方法中的綁定對象。 – DaveB
我認爲你是部分正確的,我建議把字段引用過來,這當然必須運行。但通過DataBinding它應該獨立工作,因爲您只需更改屬性,GUI線程正在觀察屬性。請糾正我,如果我錯了(我也沒有測試,只是從我記得我們是如何做到的)。 – m4mbax