2015-07-10 174 views
0

我是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); 
} 

回答

2

凍結

你的UI凍結很可能是因爲你在等待的FX Apllication主題,要解決這個有不同的方法:


的JavaFX應用程序線程

您可以將一些工作委託給FX應用程序線程,因此請參閱Platform.runLater

並非所有內容都可以在此線程上運行,但例如,在您的DeviceController中,您可以等待直到出現消息,然後調用Platform.runLater()並更新字段(您應該爲此手動)該字段轉交給控制器)​​。


數據綁定 你所描述也可以用DataBinding來實現。 有了這個,你可以定義一個SimpleStringProperty,它綁定到你的UI標籤(.bind()方法)。如果控制器必須觸發其消息,則可以設置StringProperty,UI將自行更新。)

start Task: 
    connect to serial devices 
    synchronized loop: 
     send messages 
     wait() for event to fire 
     **updateDate the DataBounded fields** 

我們被教導說,併發通知/等待 併發上一級的等待(/通知()是非常低的水平: 你描述的場景可以用這樣的。你應該嘗試使用更高級別的同步方法或助手(人們已經解決了你的問題:))

+0

我的意思是空閒的應用程序,而不是凍結它,我的錯誤。我更新了我的帖子。但我會更深入地瞭解DataBinding,以瞭解如何實現它。謝謝 – artyfrost

+0

綁定可能不會像這裏所描述的那樣工作。嘗試更新由FXAT以外的其他任何GUI元素觀察到的對象將導致運行時錯誤。所以你仍然需要更新RunLater方法中的綁定對象。 – DaveB

+0

我認爲你是部分正確的,我建議把字段引用過來,這當然必須運行。但通過DataBinding它應該獨立工作,因爲您只需更改屬性,GUI線程正在觀察屬性。請糾正我,如果我錯了(我也沒有測試,只是從我記得我們是如何做到的)。 – m4mbax

相關問題