簡體   English   中英

Future 和 ExecutorService,如何知道取消的任務何時終止?

[英]Future and ExecutorService, how to know when a cancelled task has terminated?

假設我有一些代碼使用ExecutorService啟動任務,然后調用者通過submit()方法返回的Future取消它:

execService = Executors.newSingleThreadExecutor ();
Future<String> result = execService.submit ( () -> {
  for ( ... ) {
    if ( Thread.interrupted() ) break;
    // Stuff that takes a while
  }
  return "result";
});
...
result.cancel ( true );
// I'm not sure the corresponding thread/task has finished and the call() method 
// above returned
// result.isDone() is immediately true. result.get() throws CancellationException

所以,我可以取消后台任務,讓執行者准備好重新開始。 但是,在被中斷的任務完成中斷並且相應的方法返回之前,我不能做后者。

請注意,在上面的代碼中,主流程在result.cancel()之后直接返回並且result.isCancelled()之后立即為真,同時,並行任務可能需要一段時間才能再次檢查Thread.interrupted()並終止。 在繼續主線程之前,我需要確保副任務完全完成。

此外,請注意,單線程執行器的使用是偶然的,除了這個簡單的示例之外,我想解決只有一個並行線程和多個線程正在運行的情況的問題。

確定這一點的最佳方法是什么?

到目前為止,我已經考慮過引入一個對任務和調用程序都可見的標志,或者關閉執行程序並等待它完成。 如果想多次重復使用執行程序,后一種解決方案可能效率低下,前者更好,但我想知道是否還有另一種更簡單或更規范的方法。

我也遇到了類似的問題,我最終制作了這個實用程序:

    private enum TaskState {
        READY_TO_START,
        RUNNING,
        DONE,
        MUST_NOT_START;
    }
    
    /**
     * Runs the given set of tasks ensuring that:
     * 
     * If one fails this will not return while tasks are running and once returned
     * no more tasks will start.
     * 
     * 
     * @param <V>
     * @param pool
     * @param tasksToRun
     */
    public <V> void runAndWait(ExecutorService pool, List<Callable<V>> tasksToRun) {
        
        // We use this to work around the fact that the future doesn't tell us if the task is actually terminated or not.
        List<AtomicReference<TaskState>> taskStates = new ArrayList<>();
        List<Future<V>> futures = new ArrayList<>();
        
        for(Callable<V> c : tasksToRun) {
            AtomicReference<TaskState> state = new AtomicReference<>(TaskState.READY_TO_START);
            futures.add(pool.submit(new Callable<V>() {

                @Override
                public V call() throws Exception {
                    if(state.compareAndSet(TaskState.READY_TO_START, TaskState.RUNNING)) {
                        try {
                            return c.call();
                        } finally {
                            state.set(TaskState.DONE);
                        }
                    } else {
                        throw new CancellationException();
                    }
                }
                
            }));
            taskStates.add(state);
        }
        int i = 0;
        try {
            
            // Wait for all tasks.
            for(; i < futures.size(); i++) {
                futures.get(i).get(7, TimeUnit.DAYS);
            }
        } catch(Throwable t) { 
            try {
                // If we have tasks left that means something failed.
                List<Throwable> exs = new ArrayList<>();
                final int startOfRemaining = i;
                
                // Try to stop all running tasks if any left.
                for(i = startOfRemaining; i < futures.size(); i++) {
                    try {
                        futures.get(i).cancel(true);
                    } catch (Throwable e) {
                        // Prevent exceptions from stopping us from
                        // canceling all tasks. Consider logging this.
                    }
                }
                
                // Stop the case that the task is started, but has not reached our compare and set statement. 
                taskStates.forEach(s -> s.compareAndSet(TaskState.READY_TO_START, TaskState.MUST_NOT_START));
                
                for(i = startOfRemaining; i < futures.size(); i++) {
                    try {
                        futures.get(i).get();
                    } catch (InterruptedException e) {
                        break; // I guess move on, does this make sense should we instead wait for our tasks to finish?
                    } catch (CancellationException e) {
                        // It was cancelled, it may still be running if it is we must wait for it.
                        while(taskStates.get(i).get() == TaskState.RUNNING) {
                            Thread.sleep(1);
                        }
                    } catch (Throwable t1) {
                        // Record the exception if it is interesting, so users can see why it failed.
                        exs.add(t1);
                    }
                }
                
                exs.forEach(t::addSuppressed);
            } finally {
                throw new RuntimeException(t);
            }
        }
    }

我最終包裝了每項任務,這樣我就可以知道是否所有任務都已完成。

提交您需要執行的所有任務。 由於您創建了一個單線程執行器,因此任何時候都不會有多個任務在運行。 它們將隨后執行,只有在前一個結束(完成或被中斷)后才會開始。 你可以把它想象成一個隊列。

您的代碼可能如下所示:

Future<String> result1 = execService.submit(task-1);
Future<String> result2 = execService.submit(task-2);
Future<String> result3 = execService.submit(task-3);

更新 1

你問:在主線程中繼續之前,我需要確保輔助任務完全完成。 這正是執行者的工作方式。 無論一個任務在做什么 - 執行其正常邏輯或處理中斷請求 - 線程很忙,所有其他任務都在等待它。

對於您的問題,我對一般情況感興趣:請告訴我們您如何定義一般情況。 你的意思是線程數可以大於1? 你的意思是任何任務都可以依賴於任何其他任務*? 您的意思是應該啟動什么任務的邏輯取決於其他任務的執行結果? 等等。

Java 中的執行器旨在封裝與創建和監視線程相關的開銷。 使用執行器只能實現非常簡單的工作流程。 如果你想實現一些復雜的邏輯,那么執行器不是最好的選擇。

相反,請考慮使用 Activity、Camunda 或 Bonita 等工作流引擎 然后你將能夠同時執行一些任務,你將能夠指定在任務X開始之前,它應該等到同時運行的任務A,B和C全部完成,等等。

更新 2

在你的代碼中你有

if (Thread.interrupted()) ...

但這會檢查您的線程,而不是執行程序正在執行您的任務的線程。

如果你想知道任務是如何執行的——它是否完成了它的全部工作,是否有任何異常(如 NullPOinterException),或者它是否被中斷——那么你應該使用其他方法,如下所示:

Future<String> future1 = execService.submit(task1);

try {
  String result1 = future1.get();

  if (future1.isCancelled()) {
    // Your reaction on cancellation
    ...
  } else {
    // If we are here, it means task has completed its whole work:
    // - Task was not cancelled
    // - Task thread was not interrupted
    // - There was no unhandled exception
    ...
  }
} catch (InterruptedException ie) {
  // Your reaction on interruption.
  ...
} catch (ExecutionException ee) {
  // Your reaction on unhandled exception in the task
  ...
}

如果在每個任務之后都有這樣的邏輯,代碼可能會非常復雜。 這樣的代碼在你實現一個月后可能很難理解:)

你真的有這么復雜的邏輯,以至於一個任務應該分析前一個任務的執行結果嗎? 您想根據上一個任務的執行結果來決定下一個開始什么任務嗎?

如果你唯一想確定的是前一個任務已經完成了它的所有活動——它的正常工作、它的異常處理、它對線程中斷的反應等等——那么你又在單線程執行器中有這個。 因為執行者為你做了這一切。

執行者的工作方式可能不是 100% 正確。 只需使用包含中斷的單線程執行器做一個小原型,然后告訴我們為什么它不適合您的情況。

根據Future.cancel(boolean)文檔:

嘗試取消執行此任務。 如果任務已經完成、已被取消或由於其他原因無法取消,則此嘗試將失敗。 如果成功,並且在調用 cancel 時此任務尚未啟動,則此任務永遠不會運行。 如果任務已經開始,則 mayInterruptIfRunning 參數確定是否應中斷執行此任務的線程以嘗試停止任務。

此方法返回后,對 isDone() 的后續調用將始終返回 true。 如果此方法返回 true,則對 isCancelled() 的后續調用將始終返回 true。

根據get()文檔:

V get() throws InterruptedException, ExecutionException 如有必要,等待計算完成,然后檢索其結果。

假設我們已經執行了一項任務,但出於充分的理由,我們不再關心結果。 我們可以使用 Future.cancel(boolean) 告訴執行者停止操作並中斷其底層線程:

future.cancel(true);

我們上面代碼中的Future實例永遠不會完成它的操作。

事實上,如果我們嘗試從該實例調用 get(),在調用 cancel() 之后,結果將是 CancellationException。

Future.isCancelled()會告訴我們 Future 是否已經被取消。 這對於避免獲得 CancellationException 非常有用。

調用 cancel() 可能會失敗。 在這種情況下,它的返回值將為 false。 注意 cancel() 接受一個布爾值作為參數——它控制執行這個任務的線程是否應該被中斷。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM