主流的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 {
}
分享到:
相关推荐
9. **UI更新**:在GUI应用程序中,OPC数据的变化需要实时反映在界面上。C#的`InvokeRequired`和`Invoke`方法可用于在UI线程上安全地更新控件。 10. **日志记录**:为了调试和故障排查,客户端可能包含日志记录功能...
将服务端和客户端改为Winform窗口”指的是一个关于使用DotNetty框架的教程,该教程将展示如何将DotNetty应用的服务端和客户端集成到Windows Forms(Winform)应用程序中,以创建具有图形用户界面(GUI)的网络通信...
5. **多线程编程**:为了同时处理多个客户端的连接和请求,服务器端通常会使用多线程或者异步编程模型,如C#的Task或者async/await关键字。 6. **用户界面设计**:客户端的UI设计可能使用Windows Forms或WPF,提供...
3. **图形用户界面(GUI)设计**:客户端需要一个直观且吸引人的界面,让玩家可以轻松操作。C#的Windows Forms或WPF(Windows Presentation Foundation)框架提供了创建GUI的工具。设计师会使用控件如Button、...
C#的System.Threading命名空间提供了Thread和Task类,可以用来实现异步操作,提高用户体验。 10. **文件对话框**: 让用户选择要压缩的文件或目录时,可以使用FileDialog控件。C#的System.Windows.Forms命名空间...
dstask-gui(占位符) 该项目的名称是一个占位符,非常欢迎提出建议。什么这是之上的HTTP API层和Web前端。 这是因为我在使用手机时需要管理任务。 它通过将dstask用作库来执行此操作,而不仅仅是exec dstask。 现在...
总的来说,嵌入式Linux系统中的GUI系统研究与移植是一项复杂的工作,涉及到操作系统的选择、硬件资源的配置、软件设计以及GUI库的适配。通过精心的设计和实现,可以打造出功能丰富、用户体验良好的嵌入式产品,如短...
3. GUI编程:C#中的Windows Forms和WPF(Windows Presentation Foundation)框架用于构建图形用户界面。本项目可能使用了其中的一种来实现即时通讯的界面设计。 【即时通讯原理】 1. TCP/IP协议:即时通讯通常基于...
在C#编程环境中,开发一个绘图示例、白板服务器和白板客户端涉及到多个关键知识点,这包括图形用户界面(GUI)设计、网络通信、多线程处理以及事件驱动编程。下面将详细阐述这些核心概念。 首先,C#中的绘图功能...
8. 多线程编程:因为OPC通信通常涉及异步操作,所以理解C#中的多线程和异步编程模型,如Task和async/await关键字,对于实现高效客户端至关重要。 9. UI集成:如果客户端需要用户界面,那么还需要了解WPF或WinForms...
8. **多线程**:在GUI编程中,为了保持界面的响应性,通常需要将耗时操作放在后台线程(如SwingWorker或JavaFX的Task)中执行,以免阻塞事件处理线程。 9. **数据绑定**:JavaFX提供了模型-视图-控制器(MVC)架构...
在本Java课程设计中,学生的目标是创建一个带界面的聊天室程序,旨在将传统的基于控制台的聊天程序转化为具有图形用户界面(GUI)的应用。这个设计不仅涉及到Java编程语言,还涵盖了网络通信、数据库技术和组件与...
在聊天室的设计中,服务器端需要维持一个客户端连接列表,当新的客户端连接时,将其加入列表并广播消息到所有在线的客户端。这一过程可能涉及到多线程,因为每个客户端连接都需要独立的线程来处理其发送和接收的数据...
5. **UI设计**:聊天室的用户界面(UI)可能使用Windows Forms或WPF框架构建,这两个都是C#提供的GUI库。开发者可能会使用TextBox控件显示聊天历史,Button控件触发发送消息,ListView或ListBox控件显示在线用户列表...
在Android系统中,Kiosk模式,也称为自助服务模式或专有模式,是一种特殊的应用程序部署方式,主要用于商业、教育和公共场所,使设备只能运行特定的应用程序,限制用户对系统的其他部分进行访问。...
【C#聊天程序】是一个基于C/S(客户端/服务器)架构设计的课程项目,旨在教授学生如何使用C#编程语言创建功能完善的聊天应用程序。在这个项目中,开发者将学习到C#的核心特性,如面向对象编程、网络通信以及图形用户...
5. WinForms或WPF GUI设计,控件的使用和事件处理。 6. 网络通信协议,如TCP/IP的基本原理和应用。 7. 数据序列化和反序列化,可能是字符串或自定义对象的转换。 8. 异步编程,提高服务器性能和用户体验。 对于初学...
C#提供了System.Windows.FormsAutomation命名空间,可以用来控制GUI元素,实现自动化操作。 4. **多线程编程**:为了保证辅助程序的响应速度和效率,多线程编程是必不可少的。C#提供了丰富的多线程支持,如Thread、...
JavaFX是一种用于构建桌面、移动和嵌入式设备上的富客户端应用程序的现代Java GUI框架。在JavaFX中,处理耗时任务时,如大量数据计算或网络通信,如果不使用多线程,可能会导致用户界面(UI)冻结,用户体验下降。...