不轮询监视变量变化的方法

12

我正在使用一个名为Processing的框架,它基本上是一个Java小程序。它能够执行按键事件,因为它是一个小程序。你也可以将自己的回调函数整合到父类中。我现在没有这样做,也许那就是解决方案。目前,我正在寻找更加普遍的POJO解决方案。因此,我编写了一些示例来说明我的问题。

请忽略在命令行(控制台)上使用按键事件。当然,这将是一个非常干净的解决方案,但在命令行上不可能实现,而且我的实际应用程序也不是命令行应用程序。事实上,按键事件对我来说是一个很好的解决方案,但我想了解除了与键盘有关的问题之外的事件和轮询。

这两个示例都会翻转一个布尔值。当布尔值翻转时,我想要触发某些内容。我可以在一个对象中包装这个布尔值,这样如果对象发生改变,我也可以触发事件。我只是不想通过if()语句进行无谓的轮询。

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

/*
 * Example of checking a variable for changes.
 * Uses dumb if() and polls continuously.
 */
public class NotAvoidingPolling {

    public static void main(String[] args) {

        boolean typedA = false;
        String input = "";

        System.out.println("Type 'a' please.");

        while (true) {
            InputStreamReader isr = new InputStreamReader(System.in);
            BufferedReader br = new BufferedReader(isr);

            try {
                input = br.readLine();
            } catch (IOException ioException) {
                System.out.println("IO Error.");
                System.exit(1);
            }

            // contrived state change logic
            if (input.equals("a")) {
                typedA = true;
            } else {
                typedA = false;
            }

            // problem: this is polling.
            if (typedA) System.out.println("Typed 'a'.");
        }
    }
}

运行此代码将输出:

Type 'a' please. 
a
Typed 'a'.

在一些论坛上,人们建议使用观察者模式。虽然这样可以将事件处理程序与被观察的类解耦,但我仍然需要在一个无限循环中使用if()语句。

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Observable;
import java.util.Observer;

/* 
 * Example of checking a variable for changes.
 * This uses an observer to decouple the handler feedback 
 * out of the main() but still is polling.
 */
public class ObserverStillPolling {

    boolean typedA = false;

    public static void main(String[] args) {

        // this
        ObserverStillPolling o = new ObserverStillPolling();

        final MyEvent myEvent = new MyEvent(o);
        final MyHandler myHandler = new MyHandler();
        myEvent.addObserver(myHandler); // subscribe

        // watch for event forever
        Thread thread = new Thread(myEvent);
        thread.start();

        System.out.println("Type 'a' please.");

        String input = "";

        while (true) {
            InputStreamReader isr = new InputStreamReader(System.in);
            BufferedReader br = new BufferedReader(isr);

            try {
                input = br.readLine();
            } catch (IOException ioException) {
                System.out.println("IO Error.");
                System.exit(1);
            }

            // contrived state change logic
            // but it's decoupled now because there's no handler here.
            if (input.equals("a")) {
                o.typedA = true;
            }
        }
    }
}

class MyEvent extends Observable implements Runnable {
    // boolean typedA;
    ObserverStillPolling o;

    public MyEvent(ObserverStillPolling o) {
        this.o = o;
    }

    public void run() {

        // watch the main forever
        while (true) {

            // event fire
            if (this.o.typedA) {
                setChanged();

                // in reality, you'd pass something more useful
                notifyObservers("You just typed 'a'.");

                // reset
                this.o.typedA = false;
            }

        }
    }
}

class MyHandler implements Observer {
    public void update(Observable obj, Object arg) {

        // handle event
        if (arg instanceof String) {
            System.out.println("We received:" + (String) arg);
        }
    }
}

运行此代码会输出:

Type 'a' please.
a
We received:You just typed 'a'.

如果if()语句在CPU上是一个NOOP,那我很满意。但实际上它会比较每次循环,这会导致CPU的负载增加。这与轮询一样糟糕。我可以通过睡眠或比较自上次更新以来经过的时间来限制它,但这不是事件驱动的。它只是减少了轮询的次数。那么如何更加智能地完成这个任务呢?如何在不进行轮询的情况下监视POJO的变化?

在C#中似乎有一些有趣的东西,叫做属性。我不是C#专家,所以也许这并没有我想象的那么神奇。

private void SendPropertyChanging(string property)
{
  if (this.PropertyChanging != null) {
    this.PropertyChanging(this, new PropertyChangingEventArgs(property));
  }
}
4个回答

7

是的,事件处理是解决问题的关键。你展示的C#代码片段也使用事件处理来告诉监听器属性已经改变。Java也有这些PropertyChangeEvent,它们被用于大多数Bean中。例如:Swing组件使用它们。

但是,你也可以手动完成这个过程:(虽然这不再是POJO,而更像是Java Bean)

EventBean.java

import java.util.LinkedList;
import java.util.List;

public class EventBean {

    private final List<Listener> listeners = new LinkedList<Listener>();

    protected final <T> void firePropertyChanged(final String property,
            final T oldValue, final T newValue) {
        assert(property != null);
        if((oldValue != null && oldValue.equals(newValue))
                || (oldValue == null && newValue == null))
            return;
        for(final Listener listener : this.listeners) {
            try {
                if(listener.getProperties().contains(property))
                    listener.propertyChanged(property, oldValue, newValue);
            } catch(Exception ex) {
                // log these, to help debugging
                ex.printStackTrace();
            }
        }
    }

    public final boolean addListener(final Listener x) {
        if(x == null) return false;
        return this.listeners.add(x);
    }
    public final boolean removeListener(final Listener x) {
        return this.listeners.remove(x);
    }

}

Listener.java:

import java.util.Collections;
import java.util.Set;
import java.util.TreeSet;

// Must be in same package as EventBean!
public abstract class Listener {

    private final Set<String> properties;

    public Listener(String... properties) {
        Collections.addAll(this.properties = new TreeSet<String>(), properties);
    }

    protected final Set<String> getProperties() {
        return this.properties;
    }

    public abstract <T> void propertyChanged(final String property,
            final T oldValue, final T newValue);
}

并且像这样实现您的类:

public class MyBean extends EventBean {

    private boolean typedA;

    public void setTypedA(final boolean newValue) {
        // you can do validation on the newValue here
        final boolean oldValue = typedA;
        super.firePropertyChanged("typedA", oldValue, newValue);
        this.typedA = newValue;
    }
    public boolean getTypedA() { return this.typedA; }

}

您可以将其用作:

MyBean x = new MyBean();
x.addListener(new Listener("typedA") {
    public <T> void propertyChanged(final String p,
                 final T oldValue, final T newValue) {
        System.out.println(p + " changed: " + oldValue + " to " + newValue);
        // TODO
    }
});

x.setTypedA(true);
x.setTypedA(false);
x.setTypedA(true);
x.setTypedA(true);

注意:Observable/Observer 很像事件处理。这是一种混合的事件处理程序/观察者模式,非常容易使用。它可以进一步改进,使 "firePropertyChanged" 使用反射来修改私有字段。然后所有的 setter 方法都非常干净和可维护,尽管性能可能会降低。 - Pindatjuh

7
如果你的观察者模式实现使用轮询,那么你对观察者模式的理解是错误的。在观察者模式中,改变主题状态的方法会在完成之前通知已订阅的观察者。
显然,这意味着主题必须支持观察者模式,你不能在观察者模式中使用任何普通的Java对象作为主题。
你可能还希望查看wikipedia example for the observer pattern|观察者模式的维基百科示例

维基百科的例子是我修改过的。但我认为我犯了一个错误,认为输入缓冲读取器是轮询的。它只是一个为了交互而虚构的while(true)循环。如果我将字符串拆分为一个带有setter的类,并从setter中触发事件一次,我就能理解它了。我在while循环中设置断点,认为它不必要地向观察者发送更新。它确实是轮询,但只是因为它正在等待文本输入。谢谢。 - squarism

1
事实上,多余的线程是不必要的。你应该让ObserverStillPolling扩展Observable并在input.equals("a")时通知观察者。
是的,这意味着POJO本身应该扩展Observable并自己通知观察者。

话虽如此,Observer/Observable实际上设计得很差。我建议自己编写interface PropertyChangeListener并让您的POJO实现它。然后在构造期间让POJO自行向观察者注册,或者通过检查某个地方是否适用于instanceof PropertyChangeListener来动态注册。


0
也许解决方案是使用wait()/notify()或Java的并发工具之一。这些构造避免了似乎是问题核心的“忙等待”轮询。
这些构造用于多线程。一个线程传递事件,其他线程对其做出响应。以下是一个示例:
class SyncQueue {

  private final Object lock = new Object();

  private Object o;

  /* One thread sends "event" ... */
  void send(Object o)
    throws InterruptedException
  {
    if (o == null)
      throw new IllegalArgumentException();
    synchronized (lock) {
      while (this.o != null)
        lock.wait();
      this.o = o;
      lock.notifyAll();
    }
  }

  /* Another blocks (without burning CPU) until event is received. */
  Object recv()
    throws InterruptedException
  {
    Object o;
    synchronized (lock) {
      while (this.o == null)
        lock.wait();
      o = this.o;
      this.o = null;
      lock.notifyAll();
    }
    return o;
  }

}

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