AWT队列线程中的调试异常

3
我正在开发一款Swing应用程序,其中一个组件进行了自定义绘制。当我在绘制代码中犯了一些错误并抛出异常时,很难调试。没有被调试器捕获,而是弹出一个包含异常信息的弹出窗口。此外,线程似乎重新启动,并且由于异常是编码错误的结果,它会一遍又一遍地显示。
当我足够幸运能够进入调试器(这很困难,因为应用程序发出的绘制请求越来越多),调试控制台会显示如下异常信息:
"SEVERE: Uncaught exception thrown in Thread[AWT-EventQueue-0,6,main] ....堆栈跟踪"
我的应用程序采用Scala编写,我使用IntelliJ IDEA 14。我的未捕获主线程异常已经被调试器处理得很好(在Java Exception Breakpoints中启用了Uncaught exception选项),但是AWT线程中的异常却没有被处理。
我尝试按照这个如何在Java中全局检测异常的答案中所述的安装处理程序,但我的处理程序似乎没有被触发。
我希望实现以下目标(按重要性排序):
  1. 避免AWT线程在异常时重新启动,或者至少防止弹出窗口显示
  2. 在调试器中处理未捕获的异常,而不是在控制台中打印
(注意:虽然这是Scala应用程序,但我假设Java的行为会相同,因此使用了Java标签)。

你提到异常是在paint方法中抛出的,确定吗?没有堆栈跟踪很难回答这个问题。但是,如果您知道异常来自哪里,您可以分析代码以查找错误并添加异常处理。基于异常的行上的条件断点应该会有所帮助。 - vickirk
3个回答

8
根据这个链接,您需要处理常规的ExceptionEDT Exception,而不使用旧的sun.awt.exception.handler hack(自Java 7以来已不再有效)。
以下是您的ExceptionHandler
public static class ExceptionHandler implements Thread.UncaughtExceptionHandler
{
    public void uncaughtException(Thread thread, Throwable thrown)
    {
        // TODO handle your Exception here
    }
}

使用方法:

// Regular Exception
Thread.setDefaultUncaughtExceptionHandler(new ExceptionHandler());

// EDT Exception
SwingUtilities.invokeAndWait(new Runnable()
{
    public void run()
    {
        // We are in the event dispatching thread
        Thread.currentThread().setUncaughtExceptionHandler(new ExceptionHandler());
    }
});

太好了,谢谢 - 这就是我所缺少的。你是否知道(或能够学习)为什么setDefaultUncaughtExceptionHandler对于EDT不足够?它是否有自己的处理程序?还是setDefaultUncaughtExceptionHandler不适用于已经启动的线程?你提供的文章很好,但似乎没有解释这个问题。 - Suma
我不知道。不过,这是个好问题 :) - ToYonos
目前已接受,但仍在等待赏金奖励,也许有人会提供更详细的信息。 - Suma
2
你的问题促使我进一步深入调查。实际上,根据 EventDispatchThread 源代码getDefaultUncaughtExceptionHandler 从未在 EventDispatchThread 中使用。相反,在 processException 方法中明确调用了 getUncaughtExceptionHandler()。这就解释了为什么 setDefaultUncaughtExceptionHandler 是不够的。 - ToYonos
每个应用程序只有一个事件调度线程(EDT)。我们在每次使用invokeAndWait()和invokeLater()方法时都需要调用Thread.currentThread().setUncaughtExceptionHandler(new ExceptionHandler());吗? - peterboston

0

在Java中

你的问题是异常被抛出到了另一个线程,即事件分发线程。有几种解决方案:

Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
    public void uncaughtException(Thread t, Throwable e) {
        logger.error("Uncaught exception in thread: " + t.getName, e);
    }
});

在任何情况下,原则上您应该将UI启动代码放入一个。
EventQueue.invokeLater();

或者直接调用SwingUtilities.invokeLater()

在Scala中

你的问题是异常被抛出到另一个线程,即事件分发线程。有几个解决方案:

Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
  def uncaughtException(t: Thread, e: Throwable) {
    logger.error("Uncaught exception in thread: " + t.getName, e)
  }
})

无论如何,原则上您应该将UI启动代码放在

EventQueue.invokeLater()

简单来说,回答你的两个问题,使用EventQueue,如果你想看到其他线程异常,请设置默认未捕获异常处理程序。 - Margus
正如我在问题中所写的那样,按照https://dev59.com/G4bca4cB1Zd3GeqPT0tz#27838458中所述安装的处理程序对我没有触发。它应该吗?答案相当古老,它仍然有效吗? - Suma
我不理解EventQueue与我的问题有什么关系 - 它如何影响异常处理?我在谈论来自paintComponent的异常,它已经在EDT中执行 - 我在这里应该怎么处理EventQueue? - Suma
事件队列 - 虽然异常可能来自EDT,但也可能是其他线程访问资源导致错误,例如修改应在EDT上执行的列表。根据可用信息,这似乎是回答问题的合理尝试,可惜被投票否决了。 - vickirk

0

看起来你唯一的解决方案可能是转换到Eclipse。 :-) 其他解决方案需要编码工作,而在异常处理程序中停止并不等同于在抛出异常的确切位置停止。

使用以下程序,我没有问题在Eclipse中监听捕获/未捕获的RuntimeException实例。

package lambda;

import java.awt.Dimension;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;

public class AWTExceptionTest {
    public static void main(String[] args) {
        JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        JButton button = new JButton("Test");
        button.addActionListener(e -> { throw new RuntimeException(); });
        frame.add(button);
        frame.setSize(new Dimension(50, 50));
        SwingUtilities.invokeLater(() -> frame.setVisible(true));
    }
}

这是在Eclipse调试模式下的外观。

Eclipse in Debug mode


1
我看到你在RuntimeException(运行时异常)上设置了断点,无论是Caught(已捕获)还是Uncaught(未捕获)。这可以在任何集成开发环境中完成,但它会捕获太多信息,包括代码已经处理得很好的异常。 - Suma
@Suma 好的,我有它是因为我这样设置了。在Eclipse中,配置它只监听未捕获的异常是没有问题的。 - Jagger
1
这与IntelliJ相同,因此转移到Eclipse不太可能在这方面对我有所帮助。我担心如果您尝试,您会发现EDT异常未被处理。 :( - Suma
1
@Suma 你说得对。看起来所有的异常都在EDT中被捕获了。我想这个线程中有一个大的try块,它捕获了所有Throwable实例,所以它不会死亡,也不需要重新启动。如果我找到其他解决方案,我会更新答案的。 - Jagger
1
这与我所看到的一致。令我惊讶的是,按照ToYonos的答案所述安装setUncaughtExceptionHandler可以起作用。查看源代码,这是因为pumpOneEventForFilters在您提到的大try块的catch中调用getUncaughtExceptionHandler().uncaughtException(通过processException)。我可以在处理程序中放置断点,但这不是理想的选择,因为调试器将在异常抛出时而不是在EDT捕获异常并传递给处理程序后停止。 - Suma

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接