Java中的智能异步重绘

Java中的智能异步重绘,java,multithreading,java.util.concurrent,Java,Multithreading,Java.util.concurrent,我有一个来自GUI问题的用例,我想提交给您的睿智 用例 我有一个GUI,它根据用户在GUI中设置的一些参数显示计算结果。例如,当用户移动滑块时,会触发几个事件,所有这些事件都会触发新的计算。当用户将滑块值从A调整到B时,会触发数十个事件 但是计算可能需要几秒钟,而滑块调整可以每隔100毫秒触发一次事件 如何编写一个适当的线程来监听这些事件,并对它们进行过滤,从而使结果的重新绘制变得生动?理想情况下,您会喜欢 收到第一个变更事件后立即开始新的计算 如果接收到新事件,则取消第一次计算,并使用新参数

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

用例

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

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

如何编写一个适当的线程来监听这些事件,并对它们进行过滤,从而使结果的重新绘制变得生动?理想情况下,您会喜欢

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

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

他将其放入一个扩展线程的类中:

public void doUpdate(){
如果(isInterrupted())
返回;
已同步(此){
请求++;
通知();
}
}
公开作废退出(){
中断();
已同步(此){
通知();
}
}
公开募捐{
而(!isInterrupted()){
试一试{
最终长r;
已同步(此){
r=请求;
}
//从此线程调用可刷新更新
如果(r>0)
refresh();//将触发重新计算
已同步(此){
if(r==请求){
请求=0;//重置
等待();
}
//否则,请循环以再次更新
}
}捕获(例外e){
e、 printStackTrace();
}
}
}
公共无效刷新(){
//执行计算并绘制它
...
}
每当GUI发送一个事件声明参数已更改时,我们调用
updater.doUpdate()
。这导致调用方法
refresh()
的次数大大减少。 但我无法控制这一切

另一种方式?

我想知道是否还有其他方法可以做到这一点,那就是使用jaca.concurrent类。但我无法在Executors框架中排序我应该从什么开始

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


谢谢

我将通过使用队列进一步断开GUI和控件之间的连接

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


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

我会查看SwingWorker.publish()()

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


我有一个进度对话框,显示正在处理的文件;文件的处理速度快于用户界面所能跟上的速度,我不希望处理速度慢到显示文件名;我使用了这个选项,让进程只显示发送到process()的最终文件名;在本例中,我只想向用户指出当前处理的位置,他们无论如何都不会读取所有文件名。我的UI在这方面工作得非常顺利。

看看javax.swing.SwingWorker(Java JDK中的源代码)的实现, 重点介绍了发布和处理两种方法之间的握手

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

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



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

如果您使用的是
Swing
,则
SwingWorker
提供了这方面的功能,您不必自己处理线程池

为每个请求启动一个
SwingWorker
。如果一个新的请求进入,而工作人员没有完成,您可以
取消()
它,然后启动一个新的
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
    }       
}
ThingyWorker-worker;
已执行的公共无效操作(操作事件e){
如果(worker!=null)worker.cancel();
worker=新事物worker();
worker.execute();
}
类ThingyWorker扩展SwingWorker{
@重写受保护的YOURCLASS doInBackground()引发异常{
return doSomeComputation();//应该是可中断的
}   
@覆盖受保护的void done(){
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