Java:使用SwingUtilities.invokeLater()进行调试

5
我经常使用SwingUtilities.invokeLater()。然而,在某些情况下,这样做会使调试变得困难:您无法看到调用SwingUtilities.invokeLater()的代码的堆栈跟踪,因为该代码已经结束了执行。

在调用SwingUtilities.invokeLater()时,是否有任何建议来设置某种上下文(仅用于调试目的),以便您可以找出导致UI事件的原因?


1
你找到了可接受的解决方案吗?非常想知道你最终做了什么 :) - Twister
6个回答

2

这里的其他答案大多都不错,但我想再提出另一个建议。如果您经常调用SwingUtilities.invokeLater,则有些时候可能是不必要的,特别是如果调用的唯一目的是确保Swing更改在事件线程上进行。适当时请尝试以下操作:

if (SwingUtilities.isEventDispatchThread()) {
  myRunnable.run();
} else {
  SwingUtilities.invokeLater(myRunnable);
}

SwingUtilities的invokeLater()实现中已经包含了这个功能吗? - Jason S
不行,因为invokeLater只有在所有挂起的事件都被处理后才会执行“稍后”。显然,如果您需要这种行为,就不能使用这个技巧。invokeAndWait也可以作为替代方案。 - DJClayworth

2
您可以尝试覆盖EventQueue并为已发布的事件打印堆栈跟踪。此外,在下面的示例中,每个发布的事件将被分配一个唯一的数字。当从其他invokeLater调用invokeLater时,日志中将打印文本postEvent 9 from 7
 // 将此代码放置在主类中的某个位置以覆盖队列
EventQueue eventQueue = Toolkit.getDefaultToolkit().getSystemEventQueue();
eventQueue.push(new MyEventQueue());

其中MyEventQueue类可能如下所示:

import java.awt.AWTEvent;
import java.awt.EventQueue;
import java.awt.event.InvocationEvent;
import java.util.WeakHashMap;

public class MyEventQueue extends EventQueue {
int currentNumber = 0; WeakHashMap<AWTEvent,Integer> eventIdMap = new WeakHashMap<AWTEvent,Integer>(); AWTEvent currentEvent = null;
protected void dispatchEvent(AWTEvent event) { if (event instanceof InvocationEvent) { currentEvent = event; } super.dispatchEvent(event); currentEvent = null; }
public void postEvent(AWTEvent event) { if (event instanceof InvocationEvent) { currentNumber = currentNumber + 1; eventIdMap.put(event, currentNumber); System.out.println("postEvent " + currentNumber + " " + (currentEvent != null ? "from " + eventIdMap.get(currentEvent) : "") ); for(StackTraceElement element : new RuntimeException().getStackTrace()) { System.out.println("\t" + element); } } super.postEvent(event); } }

作为澄清,我们可以通过继承EventQueue并覆盖所需的方法(例如invokeLater())来实现。这里有一个相关的例子:http://stackoverflow.com/questions/3158409 - trashgod

1

重写该方法,添加日志调用,然后调用真正的方法... 注意:您必须替换所有对原始方法的调用。

您甚至可以包装可运行对象并添加上下文编号(例如调用时间戳),当可运行对象开始时,它会打印上下文编号。

 public static void myInvokeLater(final Runnable runnable) {
   long ts = System.currentTimeMillis();
   Log.info("call to invoke later, context :" + ts);
   Runnable r = new Runnable() {
            public void run() {
                Log.info("start runnable of invokeLater with context :" + ts);
                runnable.run();
            }
        };
   SwingUtilities.invokeLater(r);
}

如果你在可运行对象的run方法中使用System.out.println(runnable)和System.out.println(this),你应该能够将它们匹配起来 :) - Chris Dennett
而且不容易被停用。在那里,你只需要添加 if (DEBUG) 然后执行操作,否则就转发到真正的 InvokeLater。 - Twister

1

我倾向于用更高级的方法替换“标准”的swingUtilites#invokeLater方法,也许是嵌入在一些“localUtilities”中,你可以将要执行的代码和源事件或线程安全副本作为参数传递给它(我猜你有一个源事件,无论其类型是什么)。


1

如果您频繁调用invokeLater,您可能需要考虑简化您的线程。

invokeLater实际上使用了可变静态变量,因此是最纯粹的邪恶。如果您从调用EventQueue.invokeLater转换为使用具有invokeLaterisDispatchThread的接口,则测试等将变得更加容易。

不幸的是,通常无法替换库使用的invokeLaterisDispatchThread


你能解释一下关于“可变静态变量”的评论吗?我不明白为什么需要在UI/非UI边界传递可变数据。 - Anon
@Anon 实现静态的 invokeLater 必须在某个地方选择一个[可变的]静态事件队列。 - Tom Hawtin - tackline
那么您建议用(可注入的)实例替换对SwingUtilities的静态调用? - Anon
@Anon 是的。(尽管我反对“可注入”这个术语 - 我更喜欢“从上面进行参数化”或“正确使用构造函数”。) - Tom Hawtin - tackline
同样适用于你的附言!我避免阅读有关依赖注入的信息数月,因为我认为它是一种邪恶的黑客技术,可以用任意的坏代码来覆盖好的代码。 - Jason S

1

你是否正在将匿名的Runnable传递给invokeLater()

如果是的话,我建议用非匿名类替换它们,这会增加几行代码,但至少会给你一定程度的可追踪性(例如:TableUpdateFromQuery)。如果你只在应用程序中的一个地方调用特定类型的更新,则此方法效果最佳。它还会引导你走向“后台活动”的道路,这可以在UI之外进行测试。


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