代码之家  ›  专栏  ›  技术社区  ›  jbaptperez

future.cancel()后面跟着future.get()终止我的线程

  •  4
  • jbaptperez  · 技术社区  · 6 年前

    我想用 遗嘱执行人 接口(使用 可赎回的 )为了启动 螺纹 (让我们称之为可调用线程),它将完成使用阻塞方法的工作。 这意味着可调用线程可以 中断例外 当主线程调用 未来。取消(真) (称之为 线程。中断() )

    我还希望我的可调用线程在使用其他阻塞方法中断时正确终止。 在代码的取消部分。

    在实现这一点时,我经历了以下行为:当我调用 未来。取消(真) 方法,正确通知可调用线程中断 但是,如果主线程立即使用 未来。 ,可调用线程 被杀死的 调用任何阻塞方法时 .

    以下 JUnit 5 片段说明了这个问题。 如果主线程不在 取消() 以及 获取() 电话。 如果我们睡眠一段时间,但还不够,我们可以看到可调用线程执行其取消工作的一半。 如果我们睡眠足够多,可调用线程将正确完成其取消工作。

    注释1 我查过了。 打断 可调用线程的状态:如预期,它正确设置了一次且仅设置了一次。

    注释2 :当在中断后逐步调试我的可调用线程时(当传入取消代码时),当进入阻塞方法时,我在几步后“松开”它(否 中断例外 好像被扔了)。

        @Test
        public void testCallable() {
    
            ExecutorService executorService = Executors.newSingleThreadExecutor();
    
            System.out.println("Main thread: Submitting callable...");
            final Future<Void> future = executorService.submit(() -> {
    
                boolean interrupted = Thread.interrupted();
    
                while (!interrupted) {
                    System.out.println("Callable thread: working...");
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        System.out.println("Callable thread: Interrupted while sleeping, starting cancellation...");
                        Thread.currentThread().interrupt();
                    }
                    interrupted = Thread.interrupted();
                }
    
                final int steps = 5;
                for (int i=0; i<steps; ++i) {
                    System.out.println(String.format("Callable thread: Cancelling (step %d/%d)...", i+1, steps));
                    try {
                        Thread.sleep(200);
                    } catch (InterruptedException e) {
                        Assertions.fail("Callable thread: Should not be interrupted!");
                    }
                }
    
                return null;
            });
    
            final int mainThreadSleepBeforeCancelMs = 2000;
            System.out.println(String.format("Main thread: Callable submitted, sleeping %d ms...", mainThreadSleepBeforeCancelMs));
    
            try {
                Thread.sleep(mainThreadSleepBeforeCancelMs);
            } catch (InterruptedException e) {
                Assertions.fail("Main thread: interrupted while sleeping.");
            }
    
            System.out.println("Main thread: Cancelling callable...");
            future.cancel(true);
            System.out.println("Main thread: Cancelable just cancelled.");
    
            // Waiting "manually" helps to test error cases:
            // - Setting to 0 (no wait) will prevent the callable thread to correctly terminate;
            // - Setting to 500 will prevent the callable thread to correctly terminate (but some cancel process is done);
            // - Setting to 1500 will let the callable thread to correctly terminate.
            final int mainThreadSleepBeforeGetMs = 0;
            try {
                Thread.sleep(mainThreadSleepBeforeGetMs);
            } catch (InterruptedException e) {
                Assertions.fail("Main thread: interrupted while sleeping.");
            }
    
            System.out.println("Main thread: calling future.get()...");
            try {
                future.get();
            } catch (InterruptedException e) {
                System.out.println("Main thread: Future.get() interrupted: Error.");
            } catch (ExecutionException e) {
                System.out.println("Main thread: Future.get() threw an ExecutionException: Error.");
            } catch (CancellationException e) {
                System.out.println("Main thread: Future.get() threw an CancellationException: OK.");
            }
    
            executorService.shutdown();
        }
    
    1 回复  |  直到 6 年前
        1
  •  5
  •   Holger    6 年前

    当你打电话 get() 关于取消 Future 你会得到一个 CancellationException 因此不会等待 Callable _s code to perform its cleanup.然后,您只是返回,而当JUnit__窆窆确定测试已完成时,所观察到的线程被终止的行为似乎是JUnit_窆窆窆清理的一部分。

    要等待完全清除,请将最后一行从

    executorService.shutdown();
    

    executorService.shutdown();
    executorService.awaitTermination(1, TimeUnit.DAYS);
    

    请注意,在方法中声明意外异常更简单。 throws 子句而不是将测试代码与 catch 子句调用 Assertions.fail . JUnit无论如何都会将这些异常报告为失败。

    然后,您可以删除整个 sleep 代码。

    可能值得把 ExecutorService 管理进入 @Before / @After 甚至 @BeforeClass / @AfterClass 方法:保持测试方法不受干扰,专注于实际测试。


    __4名。IIRC,Junit_5的名字就像 @BeforeEach / @AfterEach RESP @BeforeAll / @AfterAll