等待 long-运行 操作并显示弹出窗口
Wait for long-running operation and show popup
是否可以等待方法(比如 METHOD1)完成,但如果 运行 长于 X 秒,则调用另一个方法直到 METHOD1 returns?
一些伪代码:
method1();
startCountdown(1000); // time in millis
while (method1() still running) {
method2(); // shows a popup with spinner (Swing/AWT)
}
我猜,肯定是用并发来做的,但我不习惯并发编程。所以,我不知道如何开始。
使用的UI框架是Swing/AWT。
我以前一直在为这个问题苦苦挣扎。
我最终做的是创建一个单独的 class 来扩展 AsyncTask。向返回我的对象的 class 添加了一个 interface/listener。在我开始我的 AsyncTask 之前,我将禁用按钮并放置一个加载微调器。一旦 AsyncTask 返回,我将进行处理并重新启用按钮并取下加载微调器。粗略地说,我在示例中进行了休息调用,但它可以应用于需要一段时间的任何事情。这是比 while 循环更好的选择的原因是它不会燃烧循环检查条件。
public class RestCall extends AsyncTask {
private Context mContext;
private static final String TAG = "RestCall";
private AsyncResponse mListener;
public RestCall(Context context, URL url, AsyncResponse listener) {
this.mListener = listener;
this.mContext = context;
this.url = url;
}
public interface AsyncResponse {
void processFinish(JSONArray results);
}
@Override
protected Object doInBackground(Object[] objects) {
Log.d(TAG, "doInBackground: Thread: " + Thread.currentThread().getName());
return getResultsInJSONArray(url);
}
private JSONArray getResultsInJSONArray(URL url) {
//Here is where you will be doing the bulk of the work
//Doing a rest call and
//Processing results to JSONArray
}
@Override
protected void onPostExecute(Object o) {
super.onPostExecute(o);
Log.d(TAG, "onPostExecute: Handing off Object");
mListener.processFinish((JSONArray) o);
}
现在,在您的原始 class 中,您需要将以下内容添加到您的 class 中:
public class myClass
private restCall call;
然后从您制作的那个界面创建一个侦听器。然后将结果传递给方法。
restCall.AsyncResponse listener = results -> handleResults(results);
通过侦听器设置,您可以执行 AsyncTask。
//here is were you would throw up the loading bar.
call = new restCall(this, url, listener);
call.execute();
private void handleResults(JSONArray results){
//process what you need to
//take down loading bar
}
因此,基本思路是结合使用 SwingWorker
和 Swing Timer
。
想法是如果 Timer
在 SwingWorker
之前触发 DONE
,则执行其他一些工作流,否则停止 Timer
,例如.. .
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingWorker;
import javax.swing.Timer;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
JFrame frame = new JFrame();
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private JLabel label;
private JButton startButton;
boolean hasCompleted = false;
public TestPane() {
setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridwidth = GridBagConstraints.REMAINDER;
label = new JLabel("Waiting for you");
startButton = new JButton("Start");
add(label, gbc);
add(startButton, gbc);
startButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
startButton.setEnabled(false);
startWork();
}
});
}
@Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
protected void startWork() {
label.setText("Something wicked this way comes");
// You could build an isoloated workflow, which allowed you to pass
// three targets, the thing to be executed, the thing to be
// executed if time run over and the thing to be executed when
// the task completed (all via a single interface),
// but, you get the idea
Timer timer = new Timer(2000, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (hasCompleted) {
return;
}
label.setText("Wickedness is a bit slow today");
}
});
timer.setRepeats(false);
SomeLongRunningOperation worker = new SomeLongRunningOperation();
worker.addPropertyChangeListener(new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
switch (worker.getState()) {
case DONE:
hasCompleted = true;
timer.stop();
label.setText("All is done");
startButton.setEnabled(true);
break;
}
}
});
worker.execute();
timer.start();
}
}
public class SomeLongRunningOperation extends SwingWorker<Void, Void> {
@Override
protected Void doInBackground() throws Exception {
Thread.sleep(5000);
return null;
}
}
}
调整时间,看看您会得到什么不同的效果。
为什么要使用 SwingWorker
?因为它有自己的状态回调,这使得它更容易处理
正如我在评论中所说,您可以将工作流程提炼成一个可重复使用的概念,例如...
public class TimedTask<V> {
public static interface Task<V> {
public V execute() throws Exception;
}
public static interface TimedTaskListener<V> extends EventListener {
public void taskIsTakingLongThenExepected(TimedTask task);
public void taskDidComplete(TimedTask task, V value);
}
private Task<V> task;
private TimedTaskListener<V> listener;
private V value;
private int timeOut;
private Timer timer;
private SwingWorker<V, Void> worker;
private boolean hasCompleted = false;
public TimedTask(int timeOut, Task<V> task, TimedTaskListener<V> listener) {
this.task = task;
this.listener = listener;
this.timeOut = timeOut;
}
public V getValue() {
return value;
}
public int getTimeOut() {
return timeOut;
}
protected Task<V> getTask() {
return task;
}
protected TimedTaskListener<V> getListener() {
return listener;
}
public void execute() {
if (timer != null || worker != null) {
return;
}
hasCompleted = false;
worker = new SwingWorker<V, Void>() {
@Override
protected V doInBackground() throws Exception {
value = task.execute();
return value;
}
};
worker.addPropertyChangeListener(new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
switch (worker.getState()) {
case DONE:
hasCompleted = true;
timer.stop();
getListener().taskDidComplete(TimedTask.this, value);
break;
}
}
});
timer = new Timer(getTimeOut(), new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (hasCompleted) {
return;
}
getListener().taskIsTakingLongThenExepected(TimedTask.this);
}
});
timer.setRepeats(false);
worker.execute();
timer.start();
}
}
然后您可以将第一个示例中的 startWork
方法替换为...
protected void startWork() {
label.setText("Something wicked this way comes");
TimedTask.Task<Void> task = new TimedTask.Task<Void>() {
@Override
public Void execute() throws Exception {
Thread.sleep(5000);
return null;
}
};
TimedTask<Void> timedTask = new TimedTask(2000, task, new TimedTask.TimedTaskListener<Void>() {
@Override
public void taskIsTakingLongThenExepected(TimedTask task) {
label.setText("Wickedness is taking it's sweet time");
}
@Override
public void taskDidComplete(TimedTask task, Void value) {
label.setText("Wickedness has arrived");
startButton.setEnabled(true);
}
});
timedTask.execute();
}
虽然 SwingWorker
是完成这项工作的合适工具,但对于简单的任务,您可以使用 Thread
完成 off-edt 长任务,并使用 swing Timer
进行更新图形用户界面:
import java.awt.*;
import javax.swing.*;
import javax.swing.Timer;
public class Main{
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationByPlatform(true);
frame.add(new TestPane());
frame.pack();
frame.setVisible(true);
}
}
class TestPane extends JPanel{
private static Dimension size = new Dimension(250, 100);
private final JLabel label;
private final JButton start;
private int counter;
private Timer timer;
public TestPane() {
setLayout(new BorderLayout(10, 10));
label = new JLabel("Click START to run long process", JLabel.CENTER);
add(label,BorderLayout.NORTH);
start = new JButton("START");
start.addActionListener(e-> start() );
add(start, BorderLayout.SOUTH);
}
private void start() {
start.setEnabled(false);
int processRunTime = 10;
int updateTime = 1; //if this value >= processRunTime update() is not invoked
counter = 1;
simulateLongProcessOf(processRunTime);
timer = new Timer(1000*updateTime, e->update(counter++));
label.setText("Long process started");
timer.start();
}
private void stop() {
label.setText("Long process ended");
timer.stop();
start.setEnabled(true);
}
@Override
public Dimension preferredSize() {
return size;
}
private void simulateLongProcessOf(int seconds){
Thread t1 = new Thread(()->{
try {
Thread.sleep(1000*seconds);
} catch (InterruptedException ex) {
ex.printStackTrace();
}finally {
SwingUtilities.invokeLater(()->stop());
}
});
t1.start();
}
private void update(int count){
label.setText("Update # "+ count+" : long process is running" );
}
}
是否可以等待方法(比如 METHOD1)完成,但如果 运行 长于 X 秒,则调用另一个方法直到 METHOD1 returns?
一些伪代码:
method1();
startCountdown(1000); // time in millis
while (method1() still running) {
method2(); // shows a popup with spinner (Swing/AWT)
}
我猜,肯定是用并发来做的,但我不习惯并发编程。所以,我不知道如何开始。
使用的UI框架是Swing/AWT。
我以前一直在为这个问题苦苦挣扎。 我最终做的是创建一个单独的 class 来扩展 AsyncTask。向返回我的对象的 class 添加了一个 interface/listener。在我开始我的 AsyncTask 之前,我将禁用按钮并放置一个加载微调器。一旦 AsyncTask 返回,我将进行处理并重新启用按钮并取下加载微调器。粗略地说,我在示例中进行了休息调用,但它可以应用于需要一段时间的任何事情。这是比 while 循环更好的选择的原因是它不会燃烧循环检查条件。
public class RestCall extends AsyncTask {
private Context mContext;
private static final String TAG = "RestCall";
private AsyncResponse mListener;
public RestCall(Context context, URL url, AsyncResponse listener) {
this.mListener = listener;
this.mContext = context;
this.url = url;
}
public interface AsyncResponse {
void processFinish(JSONArray results);
}
@Override
protected Object doInBackground(Object[] objects) {
Log.d(TAG, "doInBackground: Thread: " + Thread.currentThread().getName());
return getResultsInJSONArray(url);
}
private JSONArray getResultsInJSONArray(URL url) {
//Here is where you will be doing the bulk of the work
//Doing a rest call and
//Processing results to JSONArray
}
@Override
protected void onPostExecute(Object o) {
super.onPostExecute(o);
Log.d(TAG, "onPostExecute: Handing off Object");
mListener.processFinish((JSONArray) o);
}
现在,在您的原始 class 中,您需要将以下内容添加到您的 class 中:
public class myClass
private restCall call;
然后从您制作的那个界面创建一个侦听器。然后将结果传递给方法。
restCall.AsyncResponse listener = results -> handleResults(results);
通过侦听器设置,您可以执行 AsyncTask。
//here is were you would throw up the loading bar.
call = new restCall(this, url, listener);
call.execute();
private void handleResults(JSONArray results){
//process what you need to
//take down loading bar
}
因此,基本思路是结合使用 SwingWorker
和 Swing Timer
。
想法是如果 Timer
在 SwingWorker
之前触发 DONE
,则执行其他一些工作流,否则停止 Timer
,例如.. .
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingWorker;
import javax.swing.Timer;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
JFrame frame = new JFrame();
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private JLabel label;
private JButton startButton;
boolean hasCompleted = false;
public TestPane() {
setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridwidth = GridBagConstraints.REMAINDER;
label = new JLabel("Waiting for you");
startButton = new JButton("Start");
add(label, gbc);
add(startButton, gbc);
startButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
startButton.setEnabled(false);
startWork();
}
});
}
@Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
protected void startWork() {
label.setText("Something wicked this way comes");
// You could build an isoloated workflow, which allowed you to pass
// three targets, the thing to be executed, the thing to be
// executed if time run over and the thing to be executed when
// the task completed (all via a single interface),
// but, you get the idea
Timer timer = new Timer(2000, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (hasCompleted) {
return;
}
label.setText("Wickedness is a bit slow today");
}
});
timer.setRepeats(false);
SomeLongRunningOperation worker = new SomeLongRunningOperation();
worker.addPropertyChangeListener(new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
switch (worker.getState()) {
case DONE:
hasCompleted = true;
timer.stop();
label.setText("All is done");
startButton.setEnabled(true);
break;
}
}
});
worker.execute();
timer.start();
}
}
public class SomeLongRunningOperation extends SwingWorker<Void, Void> {
@Override
protected Void doInBackground() throws Exception {
Thread.sleep(5000);
return null;
}
}
}
调整时间,看看您会得到什么不同的效果。
为什么要使用 SwingWorker
?因为它有自己的状态回调,这使得它更容易处理
正如我在评论中所说,您可以将工作流程提炼成一个可重复使用的概念,例如...
public class TimedTask<V> {
public static interface Task<V> {
public V execute() throws Exception;
}
public static interface TimedTaskListener<V> extends EventListener {
public void taskIsTakingLongThenExepected(TimedTask task);
public void taskDidComplete(TimedTask task, V value);
}
private Task<V> task;
private TimedTaskListener<V> listener;
private V value;
private int timeOut;
private Timer timer;
private SwingWorker<V, Void> worker;
private boolean hasCompleted = false;
public TimedTask(int timeOut, Task<V> task, TimedTaskListener<V> listener) {
this.task = task;
this.listener = listener;
this.timeOut = timeOut;
}
public V getValue() {
return value;
}
public int getTimeOut() {
return timeOut;
}
protected Task<V> getTask() {
return task;
}
protected TimedTaskListener<V> getListener() {
return listener;
}
public void execute() {
if (timer != null || worker != null) {
return;
}
hasCompleted = false;
worker = new SwingWorker<V, Void>() {
@Override
protected V doInBackground() throws Exception {
value = task.execute();
return value;
}
};
worker.addPropertyChangeListener(new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
switch (worker.getState()) {
case DONE:
hasCompleted = true;
timer.stop();
getListener().taskDidComplete(TimedTask.this, value);
break;
}
}
});
timer = new Timer(getTimeOut(), new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (hasCompleted) {
return;
}
getListener().taskIsTakingLongThenExepected(TimedTask.this);
}
});
timer.setRepeats(false);
worker.execute();
timer.start();
}
}
然后您可以将第一个示例中的 startWork
方法替换为...
protected void startWork() {
label.setText("Something wicked this way comes");
TimedTask.Task<Void> task = new TimedTask.Task<Void>() {
@Override
public Void execute() throws Exception {
Thread.sleep(5000);
return null;
}
};
TimedTask<Void> timedTask = new TimedTask(2000, task, new TimedTask.TimedTaskListener<Void>() {
@Override
public void taskIsTakingLongThenExepected(TimedTask task) {
label.setText("Wickedness is taking it's sweet time");
}
@Override
public void taskDidComplete(TimedTask task, Void value) {
label.setText("Wickedness has arrived");
startButton.setEnabled(true);
}
});
timedTask.execute();
}
虽然 SwingWorker
是完成这项工作的合适工具,但对于简单的任务,您可以使用 Thread
完成 off-edt 长任务,并使用 swing Timer
进行更新图形用户界面:
import java.awt.*;
import javax.swing.*;
import javax.swing.Timer;
public class Main{
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationByPlatform(true);
frame.add(new TestPane());
frame.pack();
frame.setVisible(true);
}
}
class TestPane extends JPanel{
private static Dimension size = new Dimension(250, 100);
private final JLabel label;
private final JButton start;
private int counter;
private Timer timer;
public TestPane() {
setLayout(new BorderLayout(10, 10));
label = new JLabel("Click START to run long process", JLabel.CENTER);
add(label,BorderLayout.NORTH);
start = new JButton("START");
start.addActionListener(e-> start() );
add(start, BorderLayout.SOUTH);
}
private void start() {
start.setEnabled(false);
int processRunTime = 10;
int updateTime = 1; //if this value >= processRunTime update() is not invoked
counter = 1;
simulateLongProcessOf(processRunTime);
timer = new Timer(1000*updateTime, e->update(counter++));
label.setText("Long process started");
timer.start();
}
private void stop() {
label.setText("Long process ended");
timer.stop();
start.setEnabled(true);
}
@Override
public Dimension preferredSize() {
return size;
}
private void simulateLongProcessOf(int seconds){
Thread t1 = new Thread(()->{
try {
Thread.sleep(1000*seconds);
} catch (InterruptedException ex) {
ex.printStackTrace();
}finally {
SwingUtilities.invokeLater(()->stop());
}
});
t1.start();
}
private void update(int count){
label.setText("Update # "+ count+" : long process is running" );
}
}