6

我找到了许多关于正确使用 EDT 的教程和示例,但是我想听听应该如何反其道而行之:检查一个复杂的应用程序,它有一个 Swing GUI 和许多涉及长网络操作的功能,并找到 EDT 在哪里使用不当。

我发现

SwingUtilities.isEventDispatchThread()

可用于检查一段代码是否在 EDT 内,因此我可以检查所有长操作不会发生在 SwingUtilities.isEventDispatchThread() 返回 true 的地方。

这样对吗?有没有更好的方法可以调试整个应用程序以查找 EDT 的错误使用?谢谢你。

4

2 回答 2

7

这样对吗?

是的,检查 的值SwingUtilities.isEventDispatchThread()是查看您的代码是否在事件调度线程 (EDT) 上的一种方法。

另一种方法是显示或打印Thread.currentThread().getName(). EDT 几乎总是具有名称“AWT-EventQueue-0”。

这段漂亮的代码来自文章Debugging Swing,最终总结。然而,它不是一个完整的 Swing 调试器。此代码仅检查重绘违规。

文章列出了其他更完善的调试方法。

import javax.swing.JComponent;
import javax.swing.RepaintManager;
import javax.swing.SwingUtilities;

public class CheckThreadViolationRepaintManager extends RepaintManager {
    // it is recommended to pass the complete check
    private boolean completeCheck   = true;

    public boolean isCompleteCheck() {
        return completeCheck;
    }

    public void setCompleteCheck(boolean completeCheck) {
        this.completeCheck = completeCheck;
    }

    public synchronized void addInvalidComponent(JComponent component) {
        checkThreadViolations(component);
        super.addInvalidComponent(component);
    }

    public void addDirtyRegion(JComponent component, int x, int y, int w, int h) {
        checkThreadViolations(component);
        super.addDirtyRegion(component, x, y, w, h);
    }

    private void checkThreadViolations(JComponent c) {
        if (!SwingUtilities.isEventDispatchThread()
                && (completeCheck || c.isShowing())) {
            Exception exception = new Exception();
            boolean repaint = false;
            boolean fromSwing = false;
            StackTraceElement[] stackTrace = exception.getStackTrace();
            for (StackTraceElement st : stackTrace) {
                if (repaint && st.getClassName().startsWith("javax.swing.")) {
                    fromSwing = true;
                }
                if ("repaint".equals(st.getMethodName())) {
                    repaint = true;
                }
            }
            if (repaint && !fromSwing) {
                // no problems here, since repaint() is thread safe
                return;
            }
            exception.printStackTrace();
        }
    }
}
于 2013-07-20T09:57:08.823 回答
3

检查整个应用程序的 EDT 的正确使用的一种方法是使用 java 代理。下面的代码是发布在Debugging Swing下的代理的改进版本,最终摘要。它适用于 ASM 4.1。创建一个包含 asm-all-4.1.jar(未打包)、已编译代码和将代理指定为 Premain-Class 的清单的 Jar,然后开始。

/**
 * A java agent which transforms the Swing Component classes in such a way that a stack
 * trace will be dumped or an exception will be thrown when they are accessed from a wrong thread.
 * 
 * To use it, add
 * <pre>
 * -javaagent:${workspace_loc:MyProject/tool/util/swingEDTCheck}/swingEDTCheck.jar
 * </pre>
 * 
 * to the VM arguments of a run configuration. This will cause the stack traces to be dumped.
 * 
 * Use
 * <pre>
 * -javaagent:${workspace_loc:MyProject/tool/util/swingEDTCheck}/swingEDTCheck.jar=throw
 * </pre>
 * to throw exceptions.
 * 
 */
public class SwingEDTCheckAgent {

    public static void premain(String args, Instrumentation inst) {
        boolean throwing = false;
        if ("throw".equals(args)) {
            throwing = true;
        }
        inst.addTransformer(new Transformer(throwing));
    }

    private static class Transformer implements ClassFileTransformer {

        private final boolean throwing;

        public Transformer(boolean throwing) {
            this.throwing = throwing;
        }

        @Override
        public byte[] transform(ClassLoader loader,
            String className,
            Class classBeingRedefined,
            ProtectionDomain protectionDomain,
            byte[] classfileBuffer)
            throws IllegalClassFormatException {
            // Process all classes in javax.swing package which names start with J
            if (className.startsWith("javax/swing/J")) {
                ClassReader cr = new ClassReader(classfileBuffer);
                ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_FRAMES);
                ClassVisitor cv = new EdtCheckerClassAdapter(cw, throwing);
                cr.accept(cv, 0);
                return cw.toByteArray();
            }
            return classfileBuffer;
        }
    }

    private static class EdtCheckerClassAdapter extends ClassVisitor {

        private final boolean throwing;

        public EdtCheckerClassAdapter(ClassVisitor classVisitor, boolean throwing) {
            super(Opcodes.ASM4, classVisitor);
            this.throwing = throwing;
        }

        @Override
        public MethodVisitor visitMethod(final int access,
            final String name,
            final String desc,
            final String signature,
            final String[] exceptions) {
            MethodVisitor mv =
                cv.visitMethod(access, name, desc, signature, exceptions);

            if (name.startsWith("set") || name.startsWith("get") || name.startsWith("is")) {
                return new EdtCheckerMethodAdapter(mv, throwing);
            } else {
                return mv;
            }
        }
    }

    private static class EdtCheckerMethodAdapter extends MethodVisitor {

        private final boolean throwing;

        public EdtCheckerMethodAdapter(MethodVisitor methodVisitor, boolean throwing) {
            super(Opcodes.ASM4, methodVisitor);
            this.throwing = throwing;
        }

        @Override
        public void visitCode() {
            mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/awt/EventQueue", "isDispatchThread", "()Z");
            Label l1 = new Label();
            mv.visitJumpInsn(Opcodes.IFNE, l1);
            Label l2 = new Label();
            mv.visitLabel(l2);

            if (throwing) {
                // more Aggressive: throw exception
                mv.visitTypeInsn(Opcodes.NEW, "java/lang/RuntimeException");
                mv.visitInsn(Opcodes.DUP);
                mv.visitLdcInsn("Swing Component called from outside the EDT");
                mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/RuntimeException", "<init>", "(Ljava/lang/String;)V");
                mv.visitInsn(Opcodes.ATHROW);

            } else {
                // this just dumps the Stack Trace
                mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Thread", "dumpStack", "()V");
            }
            mv.visitLabel(l1);
        }
    }
}
于 2013-09-13T13:24:18.407 回答