2014-10-08 2 views
0

Я знаю, что фактическая модель Java предназначена для совместных потоков и что она вынуждает Thread умирать, как предполагается, не произойдет.Убить нить в PropertyListener (JavaFX8)

Поскольку Thread.stop() устарел (по причинам, указанным выше). Я пытался остановить поток через слушателя BooleanProperty.

Вот MCVE:

TestStopMethod.java

package javatest; 
import javafx.beans.property.BooleanProperty; 
import javafx.beans.property.SimpleBooleanProperty; 
import javafx.beans.value.ObservableValue; 
public class TestStopMethod extends Thread { 
    private BooleanProperty amIdead = new SimpleBooleanProperty(false); 
    public void setDeath() { 
     this.amIdead.set(true); 
    } 

    @Override 
    public void run() { 
     amIdead.addListener((ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) -> { 
      System.out.println("I'm dead!!!"); 
      throw new ThreadDeath(); 
     }); 
     for(;;); 
    } 
} 

WatchDog.java

package javatest; 

import java.util.TimerTask; 

public class Watchdog extends TimerTask { 
    TestStopMethod watched; 
    public Watchdog(TestStopMethod target) { 
     watched = target; 
    } 
    @Override 
    public void run() { 
     watched.setDeath(); 
     //watched.stop(); <- Works but this is exactly what I am trying to avoid 
     System.out.println("You're dead!"); 
    } 

} 

Driver.java

package javatest; 

import java.util.*; 
import java.util.logging.Level; 
import java.util.logging.Logger; 

public class Driver { 

    public static void main(String[] args) { 
     try { 
      TestStopMethod mythread = new TestStopMethod(); 
      Timer t = new Timer(); 
      Watchdog w = new Watchdog(mythread); 
      t.schedule(w, 1000); 
      mythread.start(); 
      mythread.join(); 
      t.cancel(); 
      System.out.println("End of story"); 
     } catch (InterruptedException ex) { 
      Logger.getLogger(Driver.class.getName()).log(Level.SEVERE, null, ex); 
     } 

    } 
} 
+1

Какой поток вы думаете, бросает 'ThreadDeath' ошибку? –

+0

Я подумал, что если я добавлю слушателя к свойству Property и изменим его значение в WatchDog, он поднимет это исключение. Код, как сейчас, продолжает работать, поэтому 'mythread.join()' никогда не вызывается. – DeMarco

+1

Он будет выброшен из потока, который изменяет свойство, которое является потоком, поддерживающим экземпляр таймера. (Если вы подумаете об этом, было бы невозможно запланировать прослушивание слушателя на произвольном потоке.) Я думаю, вы запутываете объект, которому принадлежит метод, с потоком, на котором выполняется этот метод. –

ответ

1

Если вы изменили значение свойства, слушатели вызывают в том же потоке, из которого было изменено свойство (просто подумайте о том, как вы могли бы/могли бы реализовать классы свойств). Поэтому в вашем примере ошибка ThreadDeath выбрасывается из потока, поддерживающего экземпляр Timer, что на самом деле не так, как вы планировали.

Правильный способ прервать поток извне (к этому потоку) - установить флаг, а затем в потоке выполнить опрос флаг с регулярными интервалами. Это на самом деле сложнее, чем кажется, потому что для доступа к флагам требуется несколько потоков, поэтому доступ к нему должен быть правильно синхронизирован.

К счастью, для этого есть некоторые классы полезности. FutureTask, например, обертывает Runnable или Callable и предоставляет методы cancel() и isCancelled(). Если вы используете JavaFX, то javafx.concurrent API предоставляет некоторые реализации Callable и Runnable, которые делают это, а также предоставляют функциональные возможности для выполнения кода в потоке приложения FX. Посмотрите на пример documentation for javafx.concurrent.Task.

Так, например, вы могли бы сделать:

package javatest; 
public class TestStopMethod implements Runnable { 

    @Override 
    public void run() { 
     try { 
      synchronized(this) { 
       for(;;) { 
        wait(1); 
       } 
      } 
     } catch (InterruptedException exc) { 
      System.out.println("Interrupted"); 
     } 
    } 
} 

Watchdog.java:

package javatest; 

import java.util.TimerTask; 
import java.util.concurrent.Future; 

public class Watchdog extends TimerTask { 
    Future<Void> watched; 
    public Watchdog(Future<Void> target) { 
     watched = target; 
    } 
    @Override 
    public void run() { 
     watched.cancel(true); 
     //watched.stop(); <- Works but this is exactly what I am trying to avoid 
     System.out.println("You're dead!"); 
    } 
} 

Driver.java:

package javatest; 

import java.util.*; 
import java.util.concurrent.FutureTask; 
import java.util.logging.Level; 
import java.util.logging.Logger; 

public class Driver { 

    public static void main(String[] args) { 
     try { 
      FutureTask<Void> myTask = new FutureTask<>(new TestStopMethod(), null); 
      Timer t = new Timer(); 
      Watchdog w = new Watchdog(myTask); 
      t.schedule(w, 1000); 
      Thread mythread = new Thread(myTask); 
      mythread.start(); 
      mythread.join(); 
      t.cancel(); 
      System.out.println("End of story"); 
     } catch (InterruptedException ex) { 
      Logger.getLogger(Driver.class.getName()).log(Level.SEVERE, null, ex); 
     } 

    } 
} 

В приложении JavaFX, вы могли бы сделать это так. Обратите внимание, что все идет плохо, если вы пытаетесь сделать это без использования потока приложений FX, потому что флаг cancelled в FX Task должен быть обновлен в этом потоке.

package javatest; 

import javafx.concurrent.Task; 

public class TestStopMethod extends Task<Void> { 

    @Override 
    public Void call() { 
     System.out.println("Calling"); 
     while (true) { 
      if (isCancelled()) { 
       System.out.println("Cancelled"); 
       break ; 
      } 
     } 
     System.out.println("Exiting"); 
     return null ; 
    } 
} 

Watchdog.java:

package javatest; 

import java.util.TimerTask; 

import javafx.concurrent.Task; 

public class Watchdog extends TimerTask { 
    Task<Void> watched; 
    public Watchdog(Task<Void> target) { 
     watched = target; 
    } 
    @Override 
    public void run() { 
     watched.cancel(); 
     //watched.stop(); <- Works but this is exactly what I am trying to avoid 
     System.out.println("You're dead!"); 
    } 

} 

Driver.java

package javatest; 

import java.util.Timer; 
import java.util.logging.Level; 
import java.util.logging.Logger; 

import javafx.application.Application; 
import javafx.concurrent.Task; 
import javafx.concurrent.Worker; 
import javafx.scene.Scene; 
import javafx.scene.control.TextArea; 
import javafx.scene.layout.BorderPane; 
import javafx.stage.Stage; 

public class Driver extends Application { 

    @Override 
    public void start(Stage primaryStage) { 
     try { 

      TextArea console = new TextArea(); 
      BorderPane root = new BorderPane(console); 
      Scene scene = new Scene(root, 600, 400); 
      primaryStage.setScene(scene); 
      primaryStage.show(); 

      Task<Void> myTask = new TestStopMethod(); 
      Timer t = new Timer(); 
      Watchdog w = new Watchdog(myTask); 
      t.schedule(w, 1000); 
      Thread mythread = new Thread(myTask); 
      mythread.setDaemon(true); 

      myTask.stateProperty().addListener((obs, oldState, newState) -> { 
       console.appendText("State change "+oldState+" -> "+newState+"\n"); 
       if (oldState == Worker.State.RUNNING) { 
        t.cancel(); 
        console.appendText("End of Story\n"); 
       } 
      }); 
      mythread.start(); 

     } catch (Exception ex) { 
      Logger.getLogger(Driver.class.getName()).log(Level.SEVERE, null, ex); 
     } 

    } 
} 
Смежные вопросы