使用 SwingWorker 后清理

cleaning up after using a SwingWorker

注意这与终止工作线程无关。这是关于正常终止的后果。

我在美国东部时间调用 execute,所以这会产生一个 "worker thread"。
我在提交给 ExecutorServiceCallable 中调用 worker.get(),特别是 ThreadPoolExecutor.

任务结束OK(即worker.get()returns有结果,Callable也结束OK)。

我在 Executor Service 上调用 shutdown():方法正常终止。

此后我检查线程时出现了难题。这是在单元测试的上下文中,我基本上想杀死所有在上次测试期间启动的 activity。

我列出了所有线程,除了测试开始前存在的各种线程外,我还看到了这个:

# thread Thread[pool-2-thread-1,5,main], state WAITING, alive True, interrupted False, thread group main
# thread Thread[SwingWorker-pool-4-thread-1,5,main], state WAITING, alive True, interrupted False, thread group main

麻烦的是,随着测试的进行 运行,新构造的 "Application" 对象每次都会创建一个新的线程池,并且会创建越来越多的 SwingWorkers... none 这些线程似乎过渡到 Thread.State "TERMINATED"(这可能意味着它们将不再被列出)。

然后我尝试在 "SwingWorker-pool-4-thread" Thread 上调用 join... 但这会导致我当前的线程挂起。

任何人都可以解释这里发生了什么以及如何将这些线程转换为 "TERMINATED"?

以后

回复了 2 条有用的评论。 10个线程,正确的。我的想法是,如果测试失败(或发生异常),这可能会使事情陷入麻烦状态:假设在功能测试中,涉及 3 个软件,并且正在发生各种 publish/process 事情,更不用说 EDT 中的各种 Runnable 了。在进入下一个测试之前,我希望一切都关闭、关闭、结束、杀死、恢复到原始、处女状态!

为了确保所有 Runnable 都已结束,我们有 Robot.waitForIdle()。我还想出了一个策略来确保 publish/process 事情已经结束:SwingWorker, done() is executed before process() calls are finished...。但事实上,后者虽然很有用,但前提是测试代码 知道 在哪些 SW 上调用该方法...我正在寻找一个 blunderbus,它只会杀死 em石头死了。

这些 Thread 对象的 toString 清楚地显示了自从最近的测试开始以来创建的对象...但我不知道如何杀死它们。 "WAITING"不就是说像吸血鬼一样,他们可能会重新站起来干各种坏事吗?只是留下大量 "waiting" 线程,可能 运行 成百上千,似乎有点……不整洁!

更晚

啊,是的,掌握 AppContext 对象是至关重要的,因为您需要 "reset" 以防止 RejectedExecutionException。请参阅我的回答,了解如何在 Jython 中执行此操作。

我还注意到在 SW API 文档中它说

Because SwingWorker implements Runnable, a SwingWorker can be submitted to an Executor for execution.

这大概意味着您根本不必使用 SW 的默认执行程序。可能是这样,但除非在应用程序代码中有其他原因需要,否则它有点违反了不为了测试代码的方便而裁剪应用程序代码的原则...

免责声明:我不确定是否需要这样做,但是...

通过 SwingWorker 源代码挖掘后,工作人员使用 private getWorkersExecutorService 方法获取当前 JVM 的 ExecutorService

这使用 AppContext 来存储 ExecutorService 的实例,SwingWorker 的所有实例(对于 JVM)将从中提取线程以执行它们的功能。

所以,如果你做类似...

AppContext appContext = AppContext.getAppContext();
ExecutorService executorService = (ExecutorService) appContext.get(SwingWorker.class);
System.out.println(executorService);

将打印出类似于...

java.util.concurrent.ThreadPoolExecutor@4554617c[Running, pool size = 1, active threads = 1, queued tasks = 0, completed tasks = 0]

(假设您之前已经启动了 SwingWorker 否则它将 return null

但是,这是什么意思?好吧,我们现在可以直接访问所有 SwingWorkerExecutorService(世界末日现在可能开始了)

这意味着您可以关闭该服务,甚至可以将其从 AppContext 中删除,例如...

SwingWorker worker = new SwingWorker() {
    @Override
    protected Object doInBackground() throws Exception {
        System.out.println("Starting");
        Thread.sleep(10000);
        System.out.println("Ending");
        return null;
    }
};
worker.execute();
synchronized (SwingWorker.class) {
    AppContext appContext = AppContext.getAppContext();
    ExecutorService executorService = (ExecutorService) appContext.get(SwingWorker.class);
    System.out.println(executorService);
    System.out.println("Shutting down");
    executorService.shutdownNow();
    try {
        System.out.println("Waiting");
        executorService.awaitTermination(Integer.MAX_VALUE, TimeUnit.DAYS);
        System.out.println("Done");
    } catch (InterruptedException ex) {
        ex.printStackTrace();
    }
    appContext.remove(SwingWorker.class);
}

在我的测试中,它输出...

java.util.concurrent.ThreadPoolExecutor@4554617c[Running, pool size = 1, active threads = 1, queued tasks = 0, completed tasks = 0]
Shutting down
Waiting
Starting
Done

所以工人甚至没有机会开始。如果我延迟(在调用 execute 之后),shutdownNow 基本上会敲打它并且不会等待工人完成,但是如果我使用 shutdown 它会这样做,所以,那里你有。

我应该补充一点,AFAIK WAITING 意味着他们什么都不做。在 SwingWorker 的情况下,这些线程被合并,所以,是的,如果可以的话,worker 将尝试重新使用它们,但是因为在 SwingWorker 的 "normal" 操作中,工作人员需要处理 doInBackground 方法可能会爆炸的事实,它已经被设计来处理它(这就是 get 抛出 Exception 的原因),所以除非你有一个特定的用例,我不太确定你需要这么麻烦,只是说

这是对 MadProgrammer 回答的回应,感谢 him/her。

Jython 用户可以访问和使用私有方法。下面,获取所有线程,同时获取SW的ExecutorService.

运行 下面的代码确实表明,由于 shutdownNow,"SwingWorker..." 线程从末尾的线程列表中消失了。这不会阻止 3 个新线程在最后持续存在,"TimerQueue"、"AWT-Windows" 和 "Java2D Disposer"... 但这些都在 "system" ThreadGroup 中,剩下的唯一 "main" 个组线程是当前线程,所以我想说问题(如果有的话)已解决!

def get_all_threads():
    private_static_method = java.lang.Thread.getDeclaredMethod( 'getThreads' )
    private_static_method.accessible = True
    # invoking a static method: one param, None
    return private_static_method.invoke( None )

def show_all_threads():
    for thread in get_all_threads():
        print( '  # ID %d: thread %s, state %s, alive %s, interrupted %s, thread group %s' % ( 
            thread.id, thread, thread.state, thread.alive, thread.isInterrupted(), thread.threadGroup.name,) )
        if thread is java.lang.Thread.currentThread():
            print( '  # --> current thread' )

class Worker( javax.swing.SwingWorker ):
    def doInBackground( self ):
        print( 'Starting' )
        time.sleep( 3 )
        print( 'Ending' )

Worker().execute()

private_static_method = javax.swing.SwingWorker.getDeclaredMethod( 'getWorkersExecutorService' )
private_static_method.accessible = True
exec_serv = private_static_method.invoke( None )

show_all_threads()
exec_serv.shutdownNow()
print( '# shutDownNow ordered...' )
exec_serv.awaitTermination( java.lang.Integer.MAX_VALUE, java.util.concurrent.TimeUnit.DAYS )
print( '# ...terminated' )
show_all_threads()

# now "reset" the AppContext object so future executes won't raise a RejectedExecutionException
app_context = sun.awt.AppContext.getAppContext()
app_context.remove( javax.swing.SwingWorker )