主流的GUI库都是采用单线程,不管是Java的还是C++的,Java里的Swing/AWT和JFace/SWT当然都是,
因MVC的盛行,也就是观察者模式,或者说事件通知方式,如果GUI设计成多线程,开发人员必须小心翼翼的开发,稍不留神就会出现死锁或死循环,非常难用,开发人员基本上会疯掉,
据说AWT前期也想发展成多线程GUI库,这样界面更快,最终因易用性而放弃。
OK,不扯远了,即然GUI是单线程库,也就是所有的GUI操作都是在同一个GUI线程上操作的,并发操作时,有个队列进行同步排队。
在Swing里面这个特殊的线程就是著名的EDT(EventDispatchingThread),所有的GUI的变更以及相互间的事件通知都是通过这个线程进行分发的,
Swing有个不好的容错性设计,就像浏览器都支持不正规的HTML,导致写HTML的人越来越不注意正确的写法,
Swing允许你在非GUI线程直接调用GUI控件,在其内部会检查当前线程是否为EDT线程,如果不是,会自动分发到EDT队列,
这就可能使开发人员不关注EDT线程,从而导致更多的错误写法,最后就是大家都抱怨Swing写的GUI很卡,
比如:
public static void main(String[] args) {
// 在主线程直接调用UI
JFrame frame = new JFrame();
frame.show();
}
正确的写法是:
public static void main(String[] args) {
// 如果不需要等待GUI执行完成,可以用SwingUtilities.invokeLater
// 也可以用EventQueue.invokeAndWait和EventQueue.invokeLater
SwingUtilities.invokeAndWait(new Runnable() {
public void run() {
// 在EDT线程调用UI
JFrame frame = new JFrame();
frame.show();
}
});
}
而Eclipse用的JFace/SWT,当非GUI线程调用UI控件时,直接报错,虽然开始会觉得麻烦,但却减少了更多的问题,
强制你必需用下面的方式调用:
// syncExec等价于Swing的invokeAndWait
// 或者asyncExec等价于Swing的invokeLater
Display.getDefault().syncExec(new Runnable() {
public void run() {
// GUI有关的设置
}
});
上面从非UI线程调UI的问题可能不算太严重,因为Swing内部有做转发,
但反过来,在UI线程内做太多事,就很严重了,比如:
component.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
// 执行很长时间的任务,比如远程调用,大数据较验等 ...
}
});
GUI进程就会被阻塞,整个Swing窗口就卡住了,用户如果这时点一下界面,会发现界面不响应。
这也就是我们今天要解决的问题,在JDK1.6之前版本,
Swing是没有提供工具类的,你可以自己启一个线程或线程池操作非UI的逻辑,如:
component.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
new Thread(new Runnable() {
public void run() {
// 执行很长时间的任务,比如远程调用,大数据较验等 ...
}
}).start();
}
});
或者用线程池:
component.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
threadPool.execute(new Runnable {
public void run() {
// 执行很长时间的任务,比如远程调用,大数据较验等 ...
}
});
}
});
这样做的问题在于,如果我执行完后,要在界面上展示执行完的结果,
怎么办呢?当然最直接的是:
component.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
threadPool.execute(new Runnable() {
public void run() {
// 执行很长时间的任务,比如远程调用,大数据较验等 ...
// final 结果
SwingUtilities.invokeLater(new Runnable() {
public void run() {
// 在UI上显示结果
}
});
}
});
}
});
这样的代码看的头有点晕,并且还是有几个问题没解决:
1. 任务是凌散的,没有统一管理,如果要做管理面板怎么处理?
2. 任务如何取消,万一有慢到一年都执行不完的任务,想取消怎么办?
3. 没有统一的出错处理?出错了每个地方都要重复处理。
5. 任务的进度,如果要做进度条怎么处理?
6. 任务的执行过程没有事件通知,如果我想监听任务的变化,怎么处理?
针对这些问题,JDK1.6终于加了一个类来处理,叫SwingWorker,看一下它的API:
public abstract class SwingWorker<T, V> implements RunnableFuture<T> {
// Runnable
void run();
// Future<T>
void cancel(boolean);
boolean isCancelled();
boolean isDone();
T get();
T get(long, TimeUnit);
// SwingWorker<T, V>
T doInBackground() throws Exception;
void publish(V...);
void process(List<V>);
void done();
void execute();
void setProgress(int);
int getProgress();
StateValue getState();
void addPropertyChangeListener(PropertyChangeListener);
void removePropertyChangeListener(PropertyChangeListener);
void firePropertyChange(String, Object, Object);
PropertyChangeSupport getPropertyChangeSupport();
}
其问题,主要有:
1. 如果不看文档,你很难猜到它的用法,这个类集实体域,会话域,服务域于一身,非常复杂,即表示任务本身(实体域),也包含执行过程的状态(会话域),同时也具有主动执行能力(服务域)。
2. 签名上的两个泛型,第一个是总任务返回结果的类型,第二个是子任务的返回结果,然而大部分时候用不上子任务,却必需声明两个泛型。
3. 里面的潜规则也不少,比如:doInBackground()的返回值是给done()方法用的,而且要通过get()方法获取到返回值。
4. 没有Title, Message等描述信息。
5. 这个类和Thread类一样,只能执行一次,多次执行是无效的,因为其有状态,所以这样做是正确的。
它的用法如下:
new SwingWorker<List<User>, Void> () { // 没有子任务,第二个泛型声明为Void
// 独立的线程池执行doInBackground方法,执行完后,结果放在get方法的Future引用中
protected List<User> doInBackground() throws Exception {
return remoteService.findUsers();
}
// 在EDT线程执行done方法,所有与GUI的相关操作都应放在done方法中处理
protected done() {
try {
// 通过get获取doInBackground返回的结果
List<User> users = get();
// 显示到UI
tableModel.addAll(users);
} catch (Exception) {
// 捕获doInBackground抛出的异常,由get方法抛出
}
}
}.execute(); // 直接执行,内部有封装线程池和FutureTask
JSR296(Swing Application Framework)对上面的问题做了一些修补,
定义了一个Task类继承于SwingWoker,但不彻底,而且JSR296没有定稿,现阶段也不能用,
基于这种现状,我们需要扩展自己的Task方案,
接口使用如下:
TaskExecutor.execute(new TaskSupport() {
@TaskTitle("获取用户列表任务")
public void execute(TaskContext context) {
context.put("users", remoteService.findUsers());
}
public void succeeded(TaskEvent event) {
List<User> users = event.getTaskContext().get("users");
tableModel.addAll(users);
}
});
1. TaskSupport同时实现Task接口和TaskListener接口 (实体域)
2. Task表示任务本身 (实体域)
3. TaskListener为事件通知,所有的UI操作均放在Listener里处理 (实体域)
4. TaskContext为互上下文,保存执行过程中的状态 (会话域)
5. TaskEvent为事件信息 (会话域)
6. TaskExecutor提供执行能力,当然TaskExecutor可以派生出TaskService做为SPI,这样就可以同时兼容Swing/AWT和JFace/SWT (服务域)
接口签名如下:
/**
* 任务接口
*
* @author 梁飞
*/
public interface Task {
/**
* 执行任务.
* 非UI的工作,全部放在该方法内做,包括远程调用,数据转换,数据检验等,
* 其它,UI相关的工作,放在TaskListner中处理。
*
* @param context 任务上下文状态
*/
void execute(TaskContext context);
}
/**
* 任务标题
*
* @author 梁飞
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface TaskTitle {
/**
* 标题
*
* @return 标题
*/
String value();
}
/**
* 任务上下文 (非线程安全,只在一次执行过程中使用)
*
* @author 梁飞
*/
public interface TaskContext {
/**
* 保存数据
*
* @param key 键
* @param value 值
*/
void put(String key, Object value);
/**
* 获取数据
*
* @param <T> 值类型
* @param key 键
* @return 值
*/
<T> T get(String key);
/**
* 设置当前运行信息
*
* @param message 当前运行信息
*/
void setMessage(String message);
/**
* 获取当前运行信息
*
* @return 当前运行信息
*/
String getMessage();
/**
* 设置进度
*
* @param progress 进度
*/
void setProgress(int progress);
/**
* 获取进度
*
* @return 进度
*/
int getProgress();
/**
* 取消执行
*/
void cancel();
/**
* 是否已取消
*
* @return 是否已取消
*/
boolean isCancelled();
/**
* 是否失败
*
* @return 是否失败
*/
boolean isFailed();
/**
* 是否成功
*
* @return 是否成功
*/
boolean isSucceeded();
/**
* 是否结束
*
* @return 是否结束
*/
boolean isExecuted();
}
/**
* 任务执行服务 (该接口实现类必需保证线程安全)
*
* @author 梁飞
*/
public interface TaskService {
/**
* 执行任务
*
* @param task 任务
* @return 任务上下文
*/
TaskContext execute(final Task task);
}
/**
* 任务执行器 (线程安全)
*
* @author 梁飞
*/
public class TaskExecutor {
// 任务执行者服务
private static TaskService TASK_SERVICE = new SwingTaskService();
/**
* 设置任务执行者服务
*
* @param taskService 任务执行者服务
*/
public static void setTaskService(TaskService taskService) {
if (taskService == null) {
throw new IllegalArgumentException("taskService == null");
}
TASK_SERVICE = taskService;
}
/**
* 执行任务
*
* @param task 任务
* @return 任务上下文
*/
public static TaskContext execute(Task task) {
return TASK_SERVICE.execute(task);
}
}
/**
* 任务事件监听器
*
* @author 梁飞
*/
public interface TaskListener extends java.util.EventListener {
/**
* 开始执行
*
* @param event 事件信息
*/
void executing(TaskEvent event);
/**
* 执行结束 (不管是成功,失败,或取消,此方法均被调用)
*
* @param event 事件信息
*/
void executed(TaskEvent event);
/**
* 执行成功
*
* @param event 事件信息
*/
void succeeded(TaskEvent event);
/**
* 执行失败
*
* @param event 事件信息
*/
void failed(TaskEvent event);
/**
* 取消执行
*
* @param event 事件信息
*/
void cancelled(TaskEvent event);
/**
* 后台执行
*
* @param event 事件信息
*/
void backgrounded(TaskEvent event);
/**
* 前台执行
*
* @param event 事件信息
*/
void foregrounded(TaskEvent event);
/**
* 值变化
*
* @param event 事件信息
*/
void valueChanged(TaskEvent event);
/**
* 信息变化
*
* @param event 事件信息
*/
void messageChanged(TaskEvent event);
/**
* 进度变化
*
* @param event 事件信息
*/
void progressChanged(TaskEvent event);
}
/**
* 任务事件信息
*
* @author 梁飞
*/
public class TaskEvent extends java.util.EventObject {
private static final long serialVersionUID = -7251403985319158057L;
private final Task task;
private final TaskContext taskContext;
public TaskEvent(Object source, Task task, TaskContext taskContext) {
super(source);
this.task = task;
this.taskContext = taskContext;
}
/**
* 获取事件所属任务
*
* @return 任务
*/
public Task getTask() {
return task;
}
/**
* 获取事件所在任务上下文
*
* @return 任务上下文
*/
public TaskContext getTaskContext() {
return taskContext;
}
}
/**
* 事件监听器适配
*
* @author 梁飞
*/
public class TaskAdapter implements TaskListener {
@Override
public void executing(TaskEvent event) {
}
@Override
public void executed(TaskEvent event) {
}
@Override
public void succeeded(TaskEvent event) {
}
@Override
public void failed(TaskEvent event) {
}
@Override
public void cancelled(TaskEvent event) {
}
@Override
public void backgrounded(TaskEvent event) {
}
@Override
public void foregrounded(TaskEvent event) {
}
@Override
public void valueChanged(TaskEvent event) {
}
@Override
public void messageChanged(TaskEvent event) {
}
@Override
public void progressChanged(TaskEvent event) {
}
}
/**
* 任务基类
*
* @author 梁飞
*/
public abstract class TaskSupport extends TaskAdapter implements Task {
}