Как проверить приложение Swing для правильного использования EDT (Event DIspatch Thread)

109
13

Я нашел много руководств и примеров по правильному использованию EDT, однако мне хотелось бы услышать, как нужно идти наоборот: проверьте сложное приложение с графическим интерфейсом Swing и многими функциями, включающими длинные сетевые операции и найдите, где EDT используется неправильно.


Я обнаружил, что


SwingUtilities.isEventDispatchThread()

может использоваться, чтобы проверить, находится ли одна часть кода внутри EDT или нет, поэтому я мог проверить, что все длинные операции не происходят внутри мест, где SwingUtilities.isEventDispatchThread() возвращает true.


Правильно ли это? есть ли что-то лучшее, что я мог, чтобы отладить все приложение в поисках неправильного использования EDT?
Спасибо.

спросил(а) 2021-01-28T00:31:09+03:00 4 месяца, 3 недели назад
1
Решение
117

Правильно ли это?



Да, проверка значения SwingUtilities.isEventDispatchThread() - это один из способов увидеть, находится ли ваш код в потоке отправки событий (EDT) или нет.


Другой способ - отобразить или распечатать Thread.currentThread().getName(). EDT почти всегда имеет имя "AWT-EventQueue-0".

Эта замечательная часть кода взята из статьи, Отладка 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();
}
}
}

ответил(а) 2021-01-28T00:31:09+03:00 4 месяца, 3 недели назад
77

Одним из способов проверки правильного использования EDT всего приложения является использование java-агента. Код ниже представляет собой улучшенную версию агента, опубликованную в Отладка Swing, итоговое резюме. Он работает с ASM 4.1. Создайте Jar, содержащий asm-all-4.1.jar(распакованный), скомпилированный код и манифест, определяющий агент как Premain-Class и начинающий.

/**
* 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);
}
}
}

ответил(а) 2021-01-28T00:31:09+03:00 4 месяца, 3 недели назад
Ваш ответ
Введите минимум 50 символов
Чтобы , пожалуйста,
Выберите тему жалобы:

Другая проблема