ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Java] JavaFX - 스레드 동시성
    CSE/Java 2016. 1. 13. 15:12

    JavaFX는 여러 절로 구성되어 있습니다.





    Intro

    JavaFX 레이아웃(Layout)

    JavaFX 컨테이너(Container)

    JavaFX 이벤트 처리 & 속성 감시, 바인딩

    JavaFX 컨트롤(Control)

    JavaFX 메뉴바와 툴바 & 다이얼로그

    JavaFX 스레드 동시성



    JavaFX 스레드 동시성

     JavaFX UI는 스레드에 안전하지 않기 때문에 UI를 생성하고 변경하는 작업은 JavaFX Application Thread가 담당하고, 다른 작업 스레드들은 UI를 생성하거나 변경할 수 없습니다. main 스레드가 Application의 launch() 메소드를 호출하면서 생성된 JavaFX Application Thread는 start() 메소드를 실행시키면서 모든 UI를 생성합니다. 컨트롤에서 이벤트가 발생할 경우 컨트롤러의 이벤트 처리 메소드를 실행하는 것도 JavaFX Application Thread 입니다.


     JavaFX 어플리케이션을 개발할 때 주의할 점은 JavaFX Application Thread가 시간을 요하는 작업을 하지 않도록 하는 것입니다. 시간을 요하는 작업을 하게 되면 이 시간 동안에 UI는 반응하지 않고 멈춰있는 상태가 되기 때문에 다른 작업 스레드를 생성해서 처리하는 것이 좋습니다. 문제는 작업 스레드가 직접 UI를 변경할 수 없기 때문에 UI 변경이 필요한 경우 두 가지 방법을 해결해야 합니다. 


     첫 번째 방법은 Platform.runLater() 메소드를 이용하는 것이고, 두 번째는 javafx.concurrent API인 Task 또는 Service를 이용하는 것입니다.



     Platform.runLater() 메소드

      작업 스레드가 직접 UI를 변경할 수 없기 때문에 UI 변경이 필요한 경우, 작업 스레드는 UI 변경 코드를 Runnable로 생성하고, 이것을 파라미터로 해서 Platform의 정적 메소드인 runLater()를 호출 할 수 있습니다.



     





      runLatr() 메소드는 이벤트 큐(event Queue)에 Runnable을 저장하고 즉시 리턴합니다. 이벤트 큐에 저장된 Runnable 들은 저장된 순서에 따라서 JavaFX Application Thread에 의해 하나씩 실행 처리되어 UI 변경 작업을 합니다. runLater()는 지연 처리라는 뜻을 가지고 있는데, 주어진 파라미터 Runnable이 바로 처리되는 것이 아니라 이벤트 큐에 먼저 저장된 Runnable을 처리한 후에 처리됩니다.







      다음 예제는 작업 스레드가 0.1초 주기로 얻어낸 시간을 이용해서 JavaFX Application Thread가 Label 컨트롤의 text 속성을 변경합니다. Label은 UI 요소이므로 작업 스레드에서 setText() 메소드로 text 속성값을 변경할 수 없습니다. 대신 Runnable을 생성해서 Platform.runLater() 메소드를 호출했습니다. JavaFX Application Thread는 이벤트 큐에서 Runnable을 얻어 안전하게 Label의 text 속성을 변경합니다.



     * ThreadRoot.fxml


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
     
    <?xml version="1.0" encoding="UTF-8"?>
     
    <?import javafx.scene.layout.AnchorPane?>
    <?import javafx.scene.control.Label?>
    <?import javafx.scene.control.Button?>
     
    <AnchorPane xmlns:fx="http://javafx.com/fxml/1"
        prefHeight="100" prefWidth="200"
        fx:controller="javaFX.RootController">
        <children>
            <Label fx:id="lblTime" alignment="CENTER" layoutX="25.0" layoutY="15"
                prefHeight="35" prefWidth="150"
                style="-fx-background-color: black; -fx-text-fill: yellow;
                    -fx-font-size: 20; -fx-background-radius: 10;" text="00:00:00"/>
            <Button fx:id="btnStart" layoutX="46" layoutY="63" text="시작" />
            <Button fx:id="btnStop" layoutX="110" layoutY="63" text="멈춤" />
        </children>
    </AnchorPane>
     
     
    cs



     * RootController.java


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    package javaFX;
     
    import java.net.URL;
    import java.text.SimpleDateFormat;
    import java.util.Date;
    import java.util.ResourceBundle;
     
    import javafx.application.Platform;
    import javafx.event.ActionEvent;
    import javafx.fxml.FXML;
    import javafx.fxml.Initializable;
    import javafx.scene.control.Button;
    import javafx.scene.control.Label;
     
    public class RootController implements Initializable {
        @FXML private Label lblTime;
        @FXML private Button btnStart;
        @FXML private Button btnStop;
        
        private boolean stop;
        
        @Override
        public void initialize(URL loc, ResourceBundle res) {
            btnStart.setOnAction(event->handleBtnStart(event));
            btnStop.setOnAction(event->handleBtnStop(event));
        }
        
        public void handleBtnStart(ActionEvent e) {
            stop = false;
            Thread thread = new Thread() {
                @Override
                public void run() {
                    SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
                    
                    while (!stop) {
                        String strTime = sdf.format(new Date());
                        Platform.runLater(() -> {
                            lblTime.setText(strTime);
                        });
                        try { Thread.sleep(100); } catch (InterruptedException e) {}
                    }
                }
            };
            thread.setDaemon(true);
            thread.start();
        }
        
        public void handleBtnStop(ActionEvent e) {
            stop = true;
        }
    }
    cs



     * AppMain.java


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    package javaFX;
     
    import javafx.application.Application;
    import javafx.fxml.FXMLLoader;
    import javafx.scene.Parent;
    import javafx.scene.Scene;
    import javafx.stage.Stage;
     
    public class AppMain extends Application {
     
        @Override
        public void start(Stage primaryStage) throws Exception {
            Parent root = FXMLLoader.load(getClass().getResource("ThreadRoot.fxml"));
            Scene scene = new Scene(root);
            
            primaryStage.setTitle("AppMain");
            primaryStage.setScene(scene);
            primaryStage.show();
        }
     
        public static void main(String[] args) {
            launch(args);
        }
    }
     
    cs









     Task 클래스

      javafx.concurrent 패키지는 JavaFX 애플리케이션 개발에 사용할 수 있는 스레드 동시성 API를 제공하고 있습니다. 이 패키지는 Worker 인터페이스와 두 가지의 구현 클래스인 Task, Service로 구성되어 있습니다.





      Worker 인터페이스는 Task와 Service에서 공통적으로 사용할 수 있는 메소드가 선언되어 있습니다. Task 클래스는 JavaFX 애플리케이션에서 비동기 작업을 표현한 클래스이고, Service는 이러한 Task를 간편하게 시작, 취소, 재시작할 수 있는 기능을 제공합니다.



      Task 생성

       Task는 작업 스레드에서 실행되는 하나의 작업을 표현한 추상 클래스입니다. 하나의 작업을 정의할 때에는 Task를 상속해서 클래스를 생성한 후, call() 메소드를 재정의하면 됩니다. call() 메소드는 리턴값이 있는데 리턴값은 작업 결과를 말합니다. Task는 제네릭 타입인데, 타입 파라미터는 작업 결과 타입입니다 이 타입은 call() 메소드의 리턴 타입과 동일해야 합니다. 다음은 작업 결과가 Integer인 Task를 정의한 것 입니다.



    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    Task<Integer> task = new Task<Integer>() {
        @Override
        protected Integer call() throws Excpetion {
            int result = 0;
            // Code
            
            return result;
        }
    }
     
    cs




       만약 리턴값이 없는 작업이라면 타입 파라미터로 void를 사용할 수 있습니다. 이 경우 call() 메소드의 리턴값은 null 입니다.



     

      Task 실행

       Task를 작업 스레드에서 실행하려면 작업 스레드를 생성할 때 파라미터로 Task를 전달하면 됩니다. 이것은 Task가 Runnable 인터페이스를 구현하고 있기 때문입니다. 다음은 작업 스레드에서 Task를 처리하는 코드입니다.


    1
    2
    3
    4
    Task<Integer> task = new Task<Integer>() {...};
    Thread thread = new Thread(task);
    thread.setDaemon(true);
    thread.start();
    cs




       Task를 스레드풀의 작업 스레드에서 처리하려면 ExecutorService의 submit() 메소드를 호출할 때 파라미터로 전달하면 됩니다. 다음은 스레드풀에서 Task를 처리하는 코드입니다.


    1
    2
    Task<Integer> task = new Task<Integer>() {...};
    executorService.submit(task);
    cs





      Task 취소

       Task는 cancel() 메소드를 호출되면 언제든지 실행을 멈출수 있어야 합니다. 반복 작업이 포함된 Task는 주기적으로 isCancelled() 메소드를 호출해서 작업이 취소되었는지 확인하고, 작업을 종료해야 합니다. cancel() 메소드가 호출되면 isCancelled()는 true를 리턴합니다.



    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    Task<Void> task = new Task<Void>() {
        @Override
        public Void call() throws Exception {
            while (...) {
                if (isCancelled()) { break; }
                // code
            }
        
            return null;
        }
    };
    cs





       만약 Task가 Thread.sleep()과 같이 블로킹되어 있을 때, cancel() 메소드가 호출되면 InterruptedException이 발생합니다. 이 경우, 예외 처리에서 isCancelled() 메소드로 cancel() 메소드가 호출된 것인지 확인하고 작업을 종료해야 합니다.




    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    Task<Void> task = new Task<Void>() {
        @Override
        public Void call() throws Exception {
            while (...) {
                if (isCancelled()) { break; }
                // code
     
                try { Thread.sleep(100); } catch (InterruptedException e) {
                    if (isCancelled()) { break; }
                }
            
            }
        
            return null;
        }
    };
    cs





       Task는 한 번만 사용할 수 있고, 재사용할 수 없기 때문에 작업이 완료되었거나 취소된 후에 작업을 재실행하려면 Task를 다시 생성해야 합니다.





      UI 변경

       Task의 call() 메소드는 작업 스레드상에서 호출되기 때문에 UI 변경 코드를 작성할 수 없습니다. 만약 작성하게 되면 런타임 예외가 발생합니다. 대신 call() 메소드 내부에서는 updateProgress(), updateMessage() 메소드를 호출할 수 있는데, 이 메소드들은 JavaFX Application Thread로 하여금 UI를 업데이트하도록 해줍니다.


       | updateProgress() 메소드 |

       작업 스레드의 진행 정도를 ProgressBar에 나타내는 경우가 종종 있습니다. Task의 progress 속성과 ProgressBar의 progress 속성이 바인딩되어 있다면, 작업 스레드에서 updateProgress() 메소드를 호출했을 때 JavaFX Application Thread가 ProgressBar의 상태를 변경합니다.



    1
    updateProgress(double workDone, double max);
    cs




       updateProgress() 메소드의 첫 번째 파라미터는 현재 진행 정도이고, 두 번째 파라미터는 진행 완료 값입니다. 이 메소드는 Task의 progress, totalWork, workDone 속성을 업데이트합니다. 다음은 Task의 progress 속성과 ProgressBar의 progress 속성을 바인딩해서 updateProgress()가 호출될 때마다 ProgressBar의 progress 속성값을 변경하는 코드입니다.



    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    Task<Void> task = new Task<Void>() {
        @Override
        public Void call() throws Exception {
            for (int i = 1; i <= 100; i++) {
                if (isCancelled()) { break; }
                // code
                updateProgress(i, 100);
            }
            return null;
        }
    };
     
    // progress property binding
    ProgressBar progressBar = new ProgressBar();
    progressBar.progressProperty().bind(task.progressProperty());
     
    Thread thread = new Thread(task);
    thread.start();
     
    cs





       | updateMessage() 메소드 |

       작업이 진행 중이거나, 작업이 취소되었을 경우, 문자열 메시지를 전달해서 UI를 변경하고 싶다면 updateMessage()를 호출할 수 있습니다.


    1
    2
    updateMessage(String message);
     
    cs




       updateMessage()는 Task의 message 속성을 업데이트합니다. 따라서 Task의 message 속성과 UI 컨트롤의 문자열 속성을 바인딩시킬 수 있습니다. 다음은 Task의 message 속성과 Label의 text 속성을 바인딩해서 updateMessage()가 호출될 때마다 Label의 text 속성값을 변경하는 코드입니다.



    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    Task<Void> task = new Task<Void>() {
        public Void call() throws Exception {
            for (int i = 1; i <= 100; i++) {
                if (isCancelled()) { break; }
                updateMessage(String.valueOf(i));
            }
            return null;
        }
    };
     
    // String property binding
    Label label = new Label();
    label.textProperty(0.bind(task.messageProperty());
     
    Thread thread = new Thread(task);
    thread.start();
    cs




       | Platform.runLater() 메소드 |

       단순히 ProgressBar 또는 다른 컨트롤의 문자열 속성에 바인딩하는 것보다 더 복잡한 UI 변경이 필요한 경우에는 call() 메소드 내에세 Platform.runLater(new Runnable() {...})을 호출하면 됩니다.



    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    Task<Integer> task = new Task<Integer>() {
        public Void call() throws Exception {
            for (int i = 1; i <= 100; i++) {
                if (isCancelled()) { break; }
                Platform.runLater( () -> { 
                    // UI change code 
                });
            }
            return result;
        }
    };
     
    Thread thread = new Thread(task);
    thread.start();
    cs




       다음 예제는 작업 스레드가 0.1 초 주기로 0 부터 100 까지 카운팅한 값을 ProgressBar의 progress 속성 및 Label 의 text 속성과 바인딩해서 UI를 변경합니다.



     * TaskRoot.fxml


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    <?xml version="1.0" encoding="UTF-8"?>
     
    <?import javafx.scene.layout.AnchorPane?>
    <?import javafx.scene.control.ProgressBar?>
    <?import javafx.scene.control.Label?>
    <?import javafx.scene.control.Button?>
     
    <AnchorPane xmlns:fx="http://javafx.com/fxml/1"
        prefHeight="129" prefWidth="233"
        fx:controller="javaFX.RootController">
        <children>
            <ProgressBar fx:id="progressBar" layoutX="17" layoutY="23"
                prefWidth="200" progress="0" />
            <Label fx:id="label" layoutX="18.0" layoutY="57" text="진행정도: " />
            <Label fx:id="lblWorkDone" layoutX="77.0" layoutY="57"  />
            <Button fx:id="btnStart" layoutX="66" layoutY="91" text="시작" />
            <Button fx:id="btnStop" layoutX="130" layoutY="91" text="멈춤" />
        </children>
    </AnchorPane>
     
     
    cs




      * RootController.java


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    package javaFX;
     
    import java.net.URL;
    import java.util.ResourceBundle;
     
    import javafx.concurrent.Task;
    import javafx.event.ActionEvent;
    import javafx.fxml.FXML;
    import javafx.fxml.Initializable;
    import javafx.scene.control.Button;
    import javafx.scene.control.Label;
    import javafx.scene.control.ProgressBar;
     
    public class RootController implements Initializable {
        @FXML private ProgressBar progressBar;
        @FXML private Label lblWorkDone;
        @FXML private Button btnStart;
        @FXML private Button btnStop;
        
        private Task<Void> task;
        
        @Override
        public void initialize(URL loc, ResourceBundle res) {
            btnStart.setOnAction(event -> handleBtnStart(event));
            btnStop.setOnAction(event -> handleBtnStop(event));
        }
        
        public void handleBtnStart(ActionEvent e) {
            task = new Task<Void>() {
                @Override
                public Void call() throws Exception {
                    for (int i = 1; i <= 100; i++) {
                        if (isCancelled()) {
                            updateMessage("취소됨");
                            break;
                        }
                        updateProgress(i , 100);
                        updateMessage(String.valueOf(i));
                        try {Thread.sleep(100); } catch(InterruptedException e) {
                            if (isCancelled()) { 
                                updateMessage("취소됨");
                                break;
                            }
                        }
                    }
                    return null;
                }
            };
            
            progressBar.progressProperty().bind(task.progressProperty());
            lblWorkDone.textProperty().bind(task.messageProperty());
            
            Thread thread = new Thread(task);
            thread.setDaemon(true);
            thread.start();
        }
        
        public void handleBtnStop(ActionEvent e) {
            task.cancel();
        }
     
    }
    cs














      작업 상태별 콜백

       작업이 어떻게 처리되었는지에 따라 Task의 다음 세가지 메소드 중 하나가 자동 호출(콜백) 됩니다.








       이 메소드들은 Task 클래스를 작성할 때 재정의해서 어플리케이션 로직으로 재구성 할 수 있습니다. 주목할 점은 이 메소드들은 작업 스레드상에서 호출되는 것이 아니라, JavaFX Application Thread 상에서 호출되기 때문에 안전하게 UI 변경 코드를 작성할 수 있습니다. 


       다음 예제는 0 부터 100까지 합을 구하는 Task를 작성하고 작업 스레드에서 실행시킵니다. 작업 완료 결과는 succeeded() 메소드에서 얻어 Label 컨트롤에 나타냅니다.




     * TaskRoot.fxml


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
     
    <?xml version="1.0" encoding="UTF-8"?>
     
    <?import javafx.scene.layout.AnchorPane?>
    <?import javafx.scene.control.ProgressBar?>
    <?import javafx.scene.control.Label?>
    <?import javafx.scene.control.Button?>
     
    <AnchorPane xmlns:fx="http://javafx.com/fxml/1"
        prefHeight="129" prefWidth="233"
        fx:controller="javaFX.RootController">
        <children>
            <ProgressBar fx:id="progressBar" layoutX="17" layoutY="23"
                prefWidth="200" progress="0" />
            <Label layoutX="18.0" layoutY="57" text="진행정도: " />
            <Label fx:id="lblWorkDone" layoutX="77.0" layoutY="57"  />
            <Label layoutX="118.0" layoutY="57" text="작업결과: " />
            <Label fx:id="lblResult" layoutX="175.0" layoutY="57"  />
            <Button fx:id="btnStart" layoutX="66" layoutY="91" text="시작" />
            <Button fx:id="btnStop" layoutX="130" layoutY="91" text="멈춤" />
        </children>
    </AnchorPane>
     
     
    cs



     * RootController.java


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    package javaFX;
     
    import java.net.URL;
    import java.util.ResourceBundle;
     
    import javafx.concurrent.Task;
    import javafx.event.ActionEvent;
    import javafx.fxml.FXML;
    import javafx.fxml.Initializable;
    import javafx.scene.control.Button;
    import javafx.scene.control.Label;
    import javafx.scene.control.ProgressBar;
     
    public class RootController implements Initializable {
        @FXML private ProgressBar progressBar;
        @FXML private Label lblWorkDone;
        @FXML private Label lblResult;
        @FXML private Button btnStart;
        @FXML private Button btnStop;
        
        private Task<Integer> task;
        
        @Override
        public void initialize(URL loc, ResourceBundle res) {
            btnStart.setOnAction(event -> handleBtnStart(event));
            btnStop.setOnAction(event -> handleBtnStop(event));
        }
        
        public void handleBtnStart(ActionEvent e) {
            task = new Task<Integer>() {
                @Override
                protected Integer call() throws Exception {
                    int result = 0;
                    for (int i = 1; i <= 100; i++) {
                        if (isCancelled()) { break; }
                        result += i;
                        updateProgress(i , 100);
                        updateMessage(String.valueOf(i));
                        try {Thread.sleep(100); } catch(InterruptedException e) {
                            if (isCancelled()) { break; }
                        }
                    }
                    return result;
                }
                @Override
                public void succeeded() {
                    lblResult.setText(String.valueOf(getValue()));
                }
                
                public void cancelled() {
                    lblResult.setText("취소됨");
                }
                
                public void failed() {
                    lblResult.setText("실패");
                }
            };
            
            progressBar.progressProperty().bind(task.progressProperty());
            lblWorkDone.textProperty().bind(task.messageProperty());
            lblResult.setText("");
            
            Thread thread = new Thread(task);
            thread.setDaemon(true);
            thread.start();
        }
        
        public void handleBtnStop(ActionEvent e) {
            task.cancel();
        }
     
    }
    cs









     Service 클래스

      Service 클래스는 작업 스레드상에서 Task를 간편하게 시작, 취소, 재시작할 수 있는 기능을 제공합니다. Service 클래스의 목적은 작업 스레드와 JavaFX Application Thread가 올바르게 상호작용을 할 수 있도록 도와주는 것입니다.


      Service 생성

       Service는 추상 클래스이므로 새로운 Service를 생성하려면 Service를 상속받고 createTask() 메소드를 정의하면 됩니다. createTask()는 작업 스레드가 실행할 Task를 생성해서 리턴하도록 해야합니다. Service는 제네릭 타입이기 때문에 타입 파라미터는 작업 결과 타입으로 지정하면 됩니다.


    1
    2
    3
    4
    5
    6
    7
    class CustomService extends Service<Integer> {
        @Override
        protected Task<Integer> createTask() {
            Task<Integer> task = new Task<Integer>() { ... };
            return task;
        }
    }
    cs




      Service 시작, 취소, 재시작

       Service를 시작하려면 start() 메소드를 호출하면 되고, 취소하려면 cancel() 메소드를 호출하면 됩니다. 그리고 재시작이 필요한 경우 restart()를 호출하면 됩니다. 주의할 점은 이 메소드들은 JavaFX Application Thread상에서 호출해야 합니다. start()와 restart()가 호출되면 매번 createTask()가 실행되고, 리턴된 Task를 받아 작업 스레드에서 실행합니다.





      작업 상태별 콜백

       Task와 마찬가지로 Service도 작업이 어떻게 처리됐느냐에 따라서 Service의 다음 세 가지 메소드 중 하나가 자동 호출됩니다.


     







       이 메소드들은 Service 클래스를 작성할 때 재정의해서 어플리케이션 로직으로 재구성할 수 있습니다. Task와 마찬가지로 이 메소드들은 작업 스레드상에서 호출되는 것이 아니라, JavaFX Application Thread상에서 호출되기 때문에 안전하게 UI 변경 코드를 작성할 수 있습니다. 작업 결과가 있는 Service일 경우 succeeded() 메소드에서 작업 결과를 얻을 수 있습니다.


       다음 예제는 이전 예제와 동일한데, Service를 이용했다는 차이점이 있습니다.


      


      * RootController.java


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    package javaFX;
     
    import java.net.URL;
    import java.util.ResourceBundle;
     
    import javafx.concurrent.Service;
    import javafx.concurrent.Task;
    import javafx.event.ActionEvent;
    import javafx.fxml.FXML;
    import javafx.fxml.Initializable;
    import javafx.scene.control.Button;
    import javafx.scene.control.Label;
    import javafx.scene.control.ProgressBar;
     
    public class RootController implements Initializable {
        @FXML
        private ProgressBar progressBar;
        @FXML
        private Label lblWorkDone;
        @FXML
        private Label lblResult;
        @FXML
        private Button btnStart;
        @FXML
        private Button btnStop;
     
        private TimeService timeService;
     
        @Override
        public void initialize(URL loc, ResourceBundle res) {
            btnStart.setOnAction(event -> handleBtnStart(event));
            btnStop.setOnAction(event -> handleBtnStop(event));
            timeService = new TimeService();
            timeService.start();
        }
     
        class TimeService extends Service<Integer> {
            @Override
            protected Task<Integer> createTask() {
                Task<Integer> task = new Task<Integer>() {
                    @Override
                    protected Integer call() throws Exception {
                        int result = 0;
     
                        for (int i = 1; i <= 100; i++) {
                            if (isCancelled()) {
                                break;
                            }
                            result += i;
                            updateProgress(i, 100);
                            updateMessage(String.valueOf(i));
                            try {
                                Thread.sleep(100);
                            } catch (InterruptedException e) {
                                if (isCancelled()) {
                                    break;
                                }
                            }
                        }
                        return result;
                    }
                };
     
                progressBar.progressProperty().bind(task.progressProperty());
                lblWorkDone.textProperty().bind(task.messageProperty());
     
                return task;
            }
     
            @Override
            protected void cancelled() {
                lblResult.setText("취소됨");
            }
     
            @Override
            protected void failed() {
                lblResult.setText("실패");
            }
     
            @Override
            protected void succeeded() {
                lblResult.setText(String.valueOf(getValue()));
            }
            
            
        }
     
        public void handleBtnStart(ActionEvent e) {
            timeService.start();
            lblResult.setText("");
        }
     
        public void handleBtnStop(ActionEvent e) {
            timeService.cancel();
        }
     
    }
    cs










     



     



     * 이 포스트은 서적 '이것이 자바다' 를 참고하여 작성한 포스트입니다.


    댓글

Designed by Tistory.