Java中的巧妙异步重绘 [英] Clever asynchronous repaint in Java

查看:149
本文介绍了Java中的巧妙异步重绘的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

用例

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

但是计算可能需要花费几秒钟的时间,而滑块调整会每隔100毫秒触发一次事件.

如何编写一个适当的线程来侦听这些事件,并对其进行过滤,以便重新绘制结果?理想情况下,您想要类似的东西

  • 在收到第一个更改事件后立即开始新的计算;
  • 如果收到新事件,则取消第一个计算,并使用新参数启动一个新事件;
  • 但是请确保不会丢失最后一个事件,因为最后完成的计算必须是带有最后更新的参数的计算.

我尝试过的事情

我的一个朋友(A. Cardona)提出了这种Updater线程的低级方法,该方法可以防止太多事件触发计算.我将其复制粘贴到此处(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框架中进行排序,我应该从那开始.

你们中的任何一个都对类似的用例有经验吗?

谢谢

解决方案

如果您使用的是Swing,则SwingWorker为此提供了功能,您不必自己处理线程池. /p>

为每个请求触发一个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()方法都在同一线程上执行,因此它们可以有效地检查是否存在现有工作程序的引用.

请注意,这实际上是在做允许GUI取消现有操作的相同操作,除了在触发新请求时自动执行取消操作.

I have a use-case coming from a GUI problem I would like to submit to your sagacity.

Use case

I have a GUI that displays a computation result depending on some parameters the user set in a GUI. For instance, when the user moves a slider, several events are fired, that all trigger a new computation. When the user adjust the slider value from A to B, a dozens of events are fired.

But the computation can take up to several seconds, whereas the slider adjustment can fire an event every few 100 ms.

How to write a proper Thread that would listen to these events, and kind of filter them so that the repaint of the results is lively? Ideally you would like something like

  • start a new computation as soon as first change event is received;
  • cancel the first computation if a new event is received, and start a new one with the new parameters;
  • but ensure that the last event will not be lost, because the last completed computation needs to be the one with last updated parameters.

What I have tried

A friend of mine (A. Cardona) proposed this low level approach of an Updater thread that prevents too many events to trigger a computation. I copy-paste it here (GPL):

He puts this in a class that extends 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
    ...
}

Every-time an event is sent by the GUI stating that parameters have been changed, we call updater.doUpdate(). This causes the method refresh() to be called much less. But I have no control on this.

Another way?

I was wondering if there is another way to do that, that would use the jaca.concurrent classes. But I could not sort in the Executors framework what would be the one I should start with.

Does any of you have some experience with a similar use case?

Thanks

解决方案

If you're using Swing, the SwingWorker provides capabilities for this, and you don't have to deal with the thread pool yourself.

Fire off a SwingWorker for each request. If a new request comes in and the worker is not done, you can cancel() it, and just start a new SwingWorker. Regarding what the other poster said, I don't think publish() and process() are what you are looking for (although they are also very useful), since they are meant for a case where the worker might fire off events faster than the GUI can process it.

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
    }       
}

Both the action and the done() method are executed on the same thread, so they can effectively check the reference to whether there is an existing worker.

Note that effectively this is doing the same thing that allows a GUI to cancel an existing operation, except the cancel is done automatically when a new request is fired.

这篇关于Java中的巧妙异步重绘的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆