关于系统设计某些问题的补充:
今天早上精神不太好,对一些问题没有组织清楚。
回来想了想,做出了调整,现在实现的方案大致如下:
n 方案研究:EDT和单线程
n 背景知识:同步异步,串行并行,生产者/消费者模式,事件队列
n 使用目的:顺序和可预见性
n 单线程:将同步操作转为异步操作。将并行处理转换为串行顺序处理。
n 四大类UI回调方法:
* 抽象的命令,这是高层界面API的一部分
* 单个按键按下和释放的底层事件(如果触摸点设备可用的话,那么还包括触摸点事件)
* 回调Canvas类的paint()方法
* 由Display类的callSerially()方法引发的,对Runnable.run()方法的回调
所有的UI回调方法都是序列化的,所以它们绝不会并行发生。也就是,任何MIDP实现都不能在前一个回调未返回的情况下调用另外一个回调。这个性质确保了当一个事件发出的时候,它的前一个事件肯定已经处理完成。如果有多个未处理的UI回调,那么只要前一个UI回调返回了,下一个就会紧接着被调用。MIDP实现同时也要保证,使用callSerially()方法引发的run()方法要在所有未处理的repaint请求都得到满足之后才被调用。
n 一个程序应该有三种线程:
u 初始化线程:该线程主要目的是启动程序的图形用户界面(GUI)。一旦GUI启动后,对于大多数事件驱动的桌面程序来说,初始化线程的工作就结束了
u UI事件调度线程EDT:该线程负责GUI组件的绘制和更新,通过调用程序的事件处理器来响应用户交互。所有事件处理都是在EDT上进行的,程序 同UI组件和其基本数据模型的交互只允许在EDT上进行,所有运行在EDT上的任务应该尽快完成,以便UI能及时响应用户输入
u 任务线程:
n EDT的作用:
u 会调用处理事件的listener,如单击一个按钮,会产生一个按钮事件放到事件队列中,然后EDT从事件队列中获得该事件,并且调用相应的listener进行处理,该部分代码已经完成
u 重绘画面paint或者update:当我们想重绘画面的时候,会去调用repaint.其实调用repaint并不是马上去画,而是记录下要绘制的区域,具体的绘制还是由EDT操刀完成,其实就是调用callSerially,产生一个线程,放到pendingSerialCall队列当中,等待每次edt loop的最后processingSerialCalls的调用,这个方案参照了J2ME底层defaultEventHandler的实现,该部分的代码已经完成
n 使用EDT的两个原则:任何GUI请求都应该在EDT中调用;任何与GUI无关的处理不要由EDT来负责
n 使用要点
u 从其他线程访问UI组件及其事件处理器会导致界面更新和绘制错误。
u 在EDT上执行耗时任务会使程序失去响应,这会使GUI事件阻塞在队列中得不到处理。
u .应使用独立的任务线程来执行耗时计算或输入输出密集型任务,比如同数据库通信、访问网站资源、读写大树据量的文件。
n 扩展思考:
u 不好的实现:
u 好的实现:
u 按照SwingWorker的invokeLater和invokeAndWait的设计思想,我的实现是做出两个函数,在Display.java里面
/**
* Causes the runnable to be invoked on the event dispatch thread. This method
* returns immediately and will not wait for the serial call to occur
*
* @param r runnable (NOT A THREAD!) that will be invoked on the EDT serial to
* the paint and key handling events
*/
public void callSerially(Runnable r) {
synchronized (sLock) {
sPendingSerialCalls.addElement(r);
sLock.notify();
}
}
/**
* Identical to callSerially with the added benefit of waiting for the Runnable method to complete.
*
* @param r runnable (NOT A THREAD!) that will be invoked on the EDT serial to
* the paint and key handling events
* @throws IllegalStateException if this method is invoked on the event dispatch thread (e.g. during
* paint or event handling).
*/
public void callSeriallyAndWait(Runnable aRunnable) {
RunnableWrapper iWrapper = new RunnableWrapper(aRunnable, 0);
callSerially(iWrapper);
synchronized (sLock) {
while (!iWrapper.isDone()) {
try {
// poll doneness to prevent potential race conditions
sLock.wait(50);
} catch (InterruptedException err) {}
}
}
}
callSerially负责创建一个含有Runnable的特定事件,并让其在EDT中排队等待调用,当被调用时就会运行Runnable中的run方法进行派发
与callSerially一样,callSeriallyAndWait也把可运行对象排入事件派发线程的队列中,callSerially在把可运行的对象放入队列后就返回,而callSeriallyAndWait一直等待知道已启动了可运行的run方法才返回。如果一个操作在另外一个操作执行之前必须从一个组件获得信息,则callSeriallyAndWait方法是很有用的。
一般我们用到callSerially,都是为了执行事件派发线程中的代码,将一段更新UI事件的代码派发到EventQueue中,但是我们可以从事件派发线程中调用callSeriallyAndWait,却不能从事件派发线程中调用callSeriallyAndWait