7

我有一个来自 GUI 问题的用例,我想提交给您的睿智。

用例

我有一个 GUI,它根据用户在 GUI 中设置的一些参数显示计算结果。例如,当用户移动滑块时,会触发几个事件,这些事件都会触发新的计算。当用户将滑块值从 A 调整到 B 时,会触发几十个事件。

但是计算可能需要几秒钟,而滑块调整可以每 100 毫秒触发一次事件。

如何编写一个合适的线程来监听这些事件,并过滤它们以使结果的重绘更加生动?理想情况下,您会想要类似的东西

  • 收到第一个更改事件后立即开始新的计算;
  • 如果接收到新事件,则取消第一次计算,并使用新参数开始新的计算;
  • 但要确保最后一个事件不会丢失,因为最后完成的计算需要是具有最后更新参数的计算。

我试过的

我的一个朋友 (A. Cardona) 提出了更新程序线程的这种低级方法,可以防止太多事件触发计算。我在这里复制粘贴(GPL):

他把它放在一个扩展 Thread 的类中:

public void doUpdate() {
    if (isInterrupted())
        return;
    synchronized (this) {
        request++;
        notify();
    }
}

public void quit() {
    interrupt();
    synchronized (this) {
        notify();
    }
}

 public void run() {
    while (!isInterrupted()) {
        try {
            final long r;
            synchronized (this) {
                r = request;
            }
            // Call refreshable update from this thread
            if (r > 0)
                refresh(); // Will trigger re-computation
            synchronized (this) {
                if (r == request) {
                    request = 0; // reset
                    wait();
                }
                // else loop through to update again
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}


public void refresh() {
    // Execute computation and paint it
    ...
}

每次 GUI 发送一个事件表明参数已更改时,我们调用updater.doUpdate(). 这导致该方法refresh()被调用的次数要少得多。但我无法控制这一点。

其他方式?

我想知道是否有另一种方法可以使用 jaca.concurrent 类。但是我无法在 Executors 框架中排序我应该从哪个开始。

你们中有人有类似用例的经验吗?

谢谢

4

5 回答 5

4

如果您正在使用Swing,则SwingWorker提供了此功能,您不必自己处理线程池。

SwingWorker为每个请求触发一个。如果有一个新的请求进来并且工作人员没有完成,你可以cancel(),然后开始一个新的SwingWorker. 关于另一张海报所说的,我不认为publish()并且process()是您正在寻找的内容(尽管它们也非常有用),因为它们适用于工作人员触发事件的速度可能比 GUI 处理它的速度更快的情况。

ThingyWorker worker;

public void actionPerformed(ActionEvent e) {
    if( worker != null ) worker.cancel();
    worker = new ThingyWorker();
    worker.execute();
}

class ThingyWorker extends SwingWorker<YOURCLASS, Object> {
    @Override protected YOURCLASS doInBackground() throws Exception {
        return doSomeComputation(); // Should be interruptible
    }   
    @Override protected void done() {
        worker = null; // Reset the reference to worker

        YOURCLASS data;

        try {
            data = get();
        } catch (Exception e) { 
            // May be InterruptedException or ExecutionException                
            e.printStackTrace();
            return;
        }           

        // Do something with data
    }       
}

action和done()method都是在同一个线程上执行的,所以可以有效的检查引用是否存在worker。

请注意,这实际上与允许 GUI 取消现有操作的操作相同,但取消是在触发新请求时自动完成的。

于 2013-03-14T15:39:36.030 回答
1

我将通过使用队列在 GUI 和控件之间提供更大程度的断开连接。

如果BlockingQueue在两个进程之间使用a。每当控件更改时,您都可以将新设置发布到队列中。

您的图形组件可以随时读取队列并对到达的事件采取行动或根据需要丢弃它们。

于 2013-03-14T15:16:53.060 回答
1

我会调查 SwingWorker.publish() ( http://docs.oracle.com/javase/6/docs/api/javax/swing/SwingWorker.html )

Publish 允许 SwingWorker 对象的后台线程调用 process() 方法,但并非每个 publish() 调用都会导致 process() 调用。如果在 process() 返回之前进行了多个进程调用并且可以再次调用,则 SwingWorker 会将用于多个发布调用的参数连接到一个进程调用中。

我有一个进度对话框,显示正在处理的文件;文件的处理速度比 UI 跟得上它们的速度快,而且我不希望处理速度变慢以显示文件名;我使用了这个并且进程只显示发送到进程()的最终文件名;在这种情况下,我想要的只是向用户指示当前处理的位置,他们无论如何都不会读取所有文件名。我的用户界面在这方面工作得非常顺利。

于 2013-03-14T15:28:52.290 回答
1

看一下 javax.swing.SwingWorker 的实现(Java JDK 中的源代码),重点是两个方法之间的握手:publishprocess

这些不会直接适用于您的问题 - 但是它们演示了您如何将更新排队(发布)到工作线程,然后在您的工作线程(进程)中为它们提供服务。

由于您只需要最后一个工作请求,因此您甚至不需要为您的情况排队:只保留最后一个工作请求。在一小段时间(1 秒)内对“最后一个请求”进行采样,以避免每 1 秒停止/重新启动很多次,如果它已更改,则停止工作并重新启动。


您不想按原样使用发布/处理的原因是该进程始终在 Swing 事件调度线程上运行 - 根本不适合长时间运行的计算。

于 2013-03-14T15:33:38.500 回答
0

这里的关键是您希望能够取消正在进行的计算。计算必须经常检查一个条件以查看它是否需要中止。

volatile Param newParam;

Result compute(Param param)
{
    loop
        compute a small sub problem
        if(newParam!=null) // abort
            return null;  

    return result
}

将参数从事件线程切换到计算线程

synchronized void put(Param param)  // invoked by event thread
    newParam = param;
    notify();

synchronized Param take()
    while(newParam==null)
        wait();
    Param param = newParam;
    newParam=null;
    return param;

计算线程确实

public void run()
    while(true)
        Param param = take();
        Result result = compute(param);
        if(result!=null)
            paint result in event thread
于 2013-03-14T16:23:20.000 回答