2016-06-28 30 views
2

多くの計算集約型タスクを実行するGUIアプリケーションを作成しようとしています。これらのタスクには時間がかかるので、ExecutorServiceを使用して複数のスレッドで実行したいと考えています。しかし、これらのタスクが完了するのを待つことでUIがフリーズするため、ExecuterServiceの進行状況をUIで更新する独自のスレッド内にTaskとして実行します。ユーザーはStartボタンを使用してタスクを開始し、Cancelでキャンセルすることができます。ExecutorServiceを起動するJavaFXタスクを正しくキャンセルします。

import java.util.ArrayList; 
import java.util.List; 
import java.util.concurrent.Callable; 
import java.util.concurrent.Future; 
import java.util.concurrent.Executors; 
import java.util.concurrent.ExecutorService; 
import java.util.concurrent.ExecutionException; 

import javafx.application.Application; 
import javafx.concurrent.Task; 
import javafx.geometry.Insets; 
import javafx.scene.layout.HBox; 
import javafx.scene.control.Button; 
import javafx.scene.Scene; 
import javafx.stage.Stage; 

class DoWorkTask extends Task<List<Double>> { 

    // List of tasks to do in parallel 
    private final List<Callable<Double>> tasks; 

    // Initialize the tasks 
    public DoWorkTask(final int numTasks) { 

     this.tasks = new ArrayList<>(); 

     for (int i = 0; i < numTasks; ++i) { 
      final int num = i; 
      final Callable<Double> task =() -> { 
       System.out.println("task " + num + " started"); 
       return longFunction(); 
      }; 

      this.tasks.add(task); 
     } 
    } 

    @Override 
    protected List<Double> call() { 

     final ExecutorService executor = Executors.newFixedThreadPool(4); 

     // Submit all tasks to the ExecutorService 
     final List<Future<Double>> futures = new ArrayList<>(); 
     this.tasks.forEach(task -> futures.add(executor.submit(task))); 

     final List<Double> result = new ArrayList<>(); 

     // Calling task.cancel() breaks out of this 
     // function without completing the loop 
     for (int i = 0; i < futures.size(); ++i) { 
      System.out.println("Checking future " + i); 

      final Future<Double> future = futures.get(i); 

      if (this.isCancelled()) { 
       // This code is never run 
       System.out.println("Cancelling future " + i); 
       future.cancel(false); 
      } else { 
       try { 
        final Double sum = future.get(); 
        result.add(sum); 
       } catch (InterruptedException | ExecutionException e) { 
        throw new RuntimeException(e); 
       } 
      } 
     } 

     executor.shutdown(); 
     return result; 
    } 

    // Some computationally intensive function 
    private static Double longFunction() { 

     double sum = 0; 
     for (int i = 0; i < 10000000; ++i) { 
      sum += Math.sqrt(i); 
     } 

     return sum; 
    } 

} 

public class Example extends Application { 

    final Button btnStart = new Button("Start"); 
    final Button btnCancel = new Button("Cancel"); 
    final HBox box = new HBox(10, btnStart, btnCancel); 
    final Scene scene = new Scene(box); 

    @Override 
    public void start(final Stage stage) { 

     box.setPadding(new Insets(10)); 

     btnStart.setOnAction(event -> { 

      final DoWorkTask task = new DoWorkTask(100); 

      btnCancel.setOnAction(e -> task.cancel()); 

      task.setOnSucceeded(e -> System.out.println("Succeeded")); 

      task.setOnCancelled(e -> System.out.println("Cancelled")); 

      task.setOnFailed(e -> { 
       System.out.println("Failed"); 
       throw new RuntimeException(task.getException()); 
      }); 

      new Thread(task).start(); 
     }); 

     stage.setScene(scene); 
     stage.show(); 
    } 
} 

しかし、作業を開始し、Cancelボタンを押すと、call()機能は、先物の残りの部分を反復することなく、すぐに終了するようです。いくつかのサンプル出力。

task 0 started 
task 1 started 
task 2 started 
Checking future 0 
task 3 started 
Checking future 1 
task 4 started 
Checking future 2 
task 5 started 
Cancelled 
// Should continue to print Checking future x and 
// Cancelling future x, but doesn't 
task 6 started 
task 7 started 
task 8 started 
task 9 started 
... 

残りの未来はキャンセルされず、繰り返しさえされていないようです。 call()関数はただちに終了します。呼び出し可能ファイルがExecutorServiceの内部で実行されず、ただちにDoWorkTaskの内部で実行される場合、この問題は発生しません。私はむしろ困惑しています。

答えて

2

あなたの問題はここにある:

try { 
    final Double sum = future.get(); 
    result.add(sum); 
} catch (InterruptedException | ExecutionException e) { 
    throw new RuntimeException(e); 
} 

あなたがキャンセルボタンをクリックすると、それは実際にそれがfuture.get()で結果を待っているように、主なタスクを実行するスレッドを中断し、メインタスクDoWorkTaskをキャンセルInterruptedExceptionが発生しますが、現在のコードでRuntimeExceptionをスローすると、メインタスクがすぐに終了し、サブタスクを中断できなくなりますので、InterruptedExceptionがスローされた場合は続行する必要があります。

try { 
    final Double sum = future.get(); 
    result.add(sum); 
} catch (ExecutionException e) { 
    throw new RuntimeException(e); 
} catch (InterruptedException e) { 
    // log something here to indicate that the task has been interrupted. 
} 
+1

ありがとうございます。私はInterruptedExceptionsを理解していませんでしたが、通常はそうしていますが、自動的にチェックされていない例外になりました。 –

+0

InterruptedExceptionsを理解するための素晴らしい記事は、Brian Goetzの[InterruptedExceptionを扱う - あなたはそれをキャッチしましたが、今それをどうするつもりですか?](https://www.ibm.com/developerworks/library/j-jtp05236/) 。 – jewelsea

関連する問題