【问题标题】:Java: Control thread speed / change delay of threadsJava:控制线程速度/更改线程的延迟
【发布时间】:2017-02-28 02:13:15
【问题描述】:

我需要一些关于 java 线程的帮助。

我目前正在做一个项目,在运行时编译一个类并调用它的主要方法。该类代表一个区域中的一个人,它作为画布对用户可见。 这个 main 方法调用了一些其他方法。用户在编辑器中键入的方法或超类中的预定义方法。 编辑器内容可能如下所示:

main() {
 System.out.println("test users class main");
 takeAll();
 takeAll();
}

public void takeAll() {
 for (int i = 0; i < 2; i++)
    move();
 takeHoney();
 takeHoney();
 takeHoney();
 for (int i = 0; i < 2; i++)
    move();
}

上面的代码是用户以后在GUI内部的一个编辑器中输入的,当他使用某个按钮时会被编译。他应该学习命令式编程。

方法 main、move 和 takeHoney 定义在一个超类中,而 takeAll 是用户在运行时定义的方法。 我的程序添加类前缀并编译用户类。

用户应该能够通过单击 GUI 中的一些按钮来启动、暂停、恢复和终止主方法。

当我只运行 main 方法时,所有方法都完成得太快了。用户只会看到结果,看不到步骤,并且在运行时无法进行交互。

到目前为止,我创建了一个新的可运行对象并启动了一个线程。

protagonistMainMethodRunnable = new Runnable() {
    @Override
    public void run() {
        protagonist.main();
        terminateWasPressed();
    }
};
//.....
Thread thread = new Thread(protagonistMainMethodRunnable);
thread.run();

“主角”是用户类的一个实例,在运行时编译。

我不太擅长线程,也找不到在每次方法调用后创建延迟的想法。

是否有人想在 main 中的每个方法调用之后甚至在每个方法调用之后创建延迟?

编辑:James_D 的想法非常有帮助。这是适用于我的用例的类:

public class OperationQueue {

    private Queue<Runnable> operationQueue = new LinkedList<Runnable>();
    private Timeline timeline;

    public OperationQueue(double delay) {
        timeline = null;
        setDelay(delay);
    }

    public void setDelay(double seconds) { //careful: seconds > 0
        if (timeline != null)
            timeline.stop();
        System.out.println("Set keyframe duration to " + seconds + "         seconds.");
        timeline = new Timeline(new KeyFrame(Duration.seconds(seconds), e -> {
            if (!operationQueue.isEmpty()) {
                operationQueue.remove().run();
            }       
        }));
        timeline.setCycleCount(Animation.INDEFINITE);
        timeline.play();
    }

    public Queue<Runnable> getOperationQueue() {
        return operationQueue;
    }

    public void add(Runnable queueItem) {
        this.operationQueue.add(queueItem);
    }
    public void clearQueue() {
        this.operationQueue.clear();
    }
}

我将时间线移到了 setter 方法。通过这种方式,您可以在运行时更改延迟(例如使用滑块)。

【问题讨论】:

  • 你使用的是什么 GUI 库?
  • @HovercraftFullOfEels 我使用 JavaFX 作为 GUI
  • @ScaryWombat 如何在每个方法之后调用 sleep 方法?用户不必在每次调用后将其插入到他的类中。
  • @JWo 抱歉您的问题不清楚
  • 你用什么来动态编译你的代码?

标签: java multithreading javafx


【解决方案1】:

这只是我如何处理这个问题的概述,因为这个问题实在是太宽泛了。

您实际上是在问如何执行用户在代码中定义的动画。 (这是一个动画,因为您正在显示一组帧,其中每个帧都是通过执行操作来定义的,并且操作之间存在时间间隔。)

考虑创建一个操作队列来执行:

private class UI {

    private Queue<Runnable> operationQueue = new LinkedList<Runnable>();

    public Queue<Runnable> getOperationQueue() {
        return operationQueue();
    }

    // ...
}

现在您可以通过定期检查队列的Timeline 运行动画,如果其中有内容,则执行该操作:

public UI () {

    // set up ui, etc...

    Timeline timeline = new Timeline(new KeyFrame(Duration.seconds(1), e-> {
        if (! operationQueue.isEmpty()) {
            operationQueue.remove().run();
        }
    }));
    timeline.setCycleCount(Animation.INDEFINITE);
    timeline.play();
}

现在将您预定义的基本方法设为私有,并定义将这些私有方法提交到队列的公共方法:

public class BaseClass {

    private final UI ui = ... ;

    private void doMove() {
        // implementation here...
    }

    public void move() {
        ui.getOperationQueue().add(this::doMove);
    }

    private void doTakeHoney() {
        // implementation here...
    }

    public void takeHoney() {
        ui.getOperationQueue().add(this::doTakeHoney);
    }
}

请注意,这里实际上根本没有线程。一切都在 FX 应用程序线程上;时间由Timeline 控制。

【讨论】:

  • 谢谢。到目前为止,这听起来非常好。虽然我的大学给了我使用线程的任务,但我明天会尝试这个。当队列处于活动状态时,GUI 会被阻塞吗?
  • @JWo UI 不会被阻止。如果必须使用线程,则需要在每次调用之间插入Thread.sleep(),将每个实际调用包装在Platform.runLater(...) 中,然后在另一个线程上执行整个序列。
  • 好的。我现在试了一下,效果很好。取消 main 方法也有效。我认为在工作几分钟内也可以暂停和恢复。我真的不明白如何在 javafx 中创建与 gui 一起正常工作的线程。这对我帮助很大。谢谢。
  • 我刚刚发现了这个解决方案的一个新问题。我有三个方法可以添加到队列中,但是有返回值(都是布尔值)。返回值很重要。用户可以在 GUI 中键入 if (safeToGo) { move(); }。你知道如何在不丢失返回值的情况下将这些方法添加到队列中吗?我的第一个想法是将方法的结果缓存在成员变量中,在某些操作后重置它们并将方法返回类型更改为 void。但这对我来说不是很好。我想知道是否有其他方法可以解决这个问题。
  • *可以加入队列
【解决方案2】:

你可以重写超类的你想慢跑的方法,比如move():

void move() throws Exception {
  Thread.sleep(1000);
  super.move();
  Thread.sleep(1000);
}

如果你想让你的代码可以被UI按钮控制,那么你将添加一些开关。

【讨论】:

  • 感谢您的想法。也许还不够清楚,我的第一个代码示例不是我写的。这就是用户应该在运行时编写的内容。这样他应该学习一些基本的编程。因此,他不会调用超类或睡眠函数中的方法。
  • 对不起,我刚才不明白你的意思。 AOP 可以帮助你拦截用户的目标方法。
  • 你能告诉我什么是AOP吗?我不熟悉这个缩写。谢谢。
  • @JWo AOP = "面向方面的编程"。这是一种拦截方法调用并向这些调用添加功能的技术。它需要一个 AOP 框架来为您执行此操作。
  • @simiam 这个建议的问题在于线程。假设 move() 更改 UI,您必须在 FX 应用程序线程上执行 move()。另一方面,您不能阻止该线程,因为您会使 UI 无响应,并阻止它在调用之间实际重新渲染。
【解决方案3】:

我认为您正在寻找的是在 javascript 中称为 setTimeout 的内容。不幸的是,Java 没有这样的方法,你必须依靠Thread.sleep 来模拟它。同样,不幸的是,在 JavaFX 应用程序线程上调用 Thread.sleep 会挂起应用程序,因此您需要使用 JavaFX Timeline

您可以做的是声明一个接口,它将为您自动执行此操作:

class Delayed {
    private static long totalMillis;
    private static Timeline timeline = new Timeline();
    static void setTimeout(Runnable r, long millis) {
        timeline.getKeyFrames().add(new KeyFrame(new Duration(totalMillis += millis), event -> r.run()));
    }
    static void play() {
        timeline.play();
    }
}

方法调用

System.out.println("Hello World!");

然后变成:

Delayed.setTimeout(() ->System.out.println("Hello World!"), 1000);
Delayed.play();

我的建议是你使用你拥有的代码(用户输入的那个),虽然它仍然是 String,但你将所有方法调用转换为 Delayed.setTimeout 调用,比如所以:

final int TIMEOUT = 1000;
StringBuilder newCode = new StringBuilder();
Arrays.stream(codeString.split("\n")).forEach(s ->
{
    if (s.endsWith(";")) newCode.append("Delayed.setTimeout(() -> ")
            .append(s.substring(0, s.length() - 1)) //remove ";"
            .append(", ").append(TIMEOUT)
            .append(");")
            .append("\n");
    else {
        newCode.append(s + "\n");
    }
});
newCode.append("Delayed.play();");

当然,s.endsWith(";") 检查非常幼稚,不适用于 if 语句、do/while 循环或多行语句等内容。我找不到一种更有效的方法来检查一行是否是完整的语句,但这对于非常简单的代码应该可以正常工作,例如您关心的代码。

编辑现在正在处理 JavaFX 应用程序线程。

【讨论】:

  • 这就是问题所在。实际的“目标”可运行对象会修改 UI,因此它必须在 UI 线程上执行。但是,如果您在该线程上执行 Thread.sleep(...),您 1. 使 UI 无响应,并且 2. 阻止它实际呈现任何更新。因此,您不会看到所有单独的步骤。您根本无法通过插入对Thread.sleep(...) 的调用来做到这一点;您必须控制各个步骤的执行,或者使用动画(更简单,恕我直言),或者将整个事情包装在另一个线程中并将各个调用包装在 Platform.runLater(...) 中。
  • @James_D 感谢您的评论。我看到了这个问题(请参阅我回答的最后一段),并且根据 OP 尝试使用 Thread.sleep 延迟方法来判断,这些方法是否正在运行应用程序线程并不是很清楚。我正在做某事,将在 2 分钟内编辑此答案。欢呼
  • 哦,是的,很抱歉您确实提到了这一点。我认为在 OP 中很清楚,尽管他的方法更新了 UI(“作为画布对用户可见”)。
猜你喜欢
  • 2011-10-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-08-31
  • 1970-01-01
  • 1970-01-01
  • 2015-10-31
  • 1970-01-01
相关资源
最近更新 更多