Swing应用程序员常见的错误是误用Swing事件调度线程(Event DispatchThread,EDT)。他们要么从非UI线程访问UI组件;要么不考虑事件执行顺序;要么不使用独立任务线程而在EDT线程上执行耗时任务,结果使编写的应用程序变得响应迟钝、速度很慢。耗时计算和输入/输出(IO)密集型任务不应放在SwingEDT上运行。发现这种问题的代码并不容易,但Java SE6提供了javax.swing.SwingWorker类,使修正这种代码变得更容易。

使用SwingWorker,程序能启动一个任务线程来异步查询,并马上返回EDT线程,允许EDT继续执行后续的UI事件。

        SwingWorker类帮你管理任务线程和EDT之间的交互,尽管SwingWorker不能解决并发线程中遇到的所有问题,但的确有助于分离SwingEDT和任务线程,使它们各负其责:对于EDT来说,就是绘制和更新界面,并响应用户输入;对于任务线程来说,就是执行和界面无直接关系的耗时任务和I/O密集型操作。

SwingWorker结构

SwingWoker实现了java.util.concurrent.RunnableFuture接口。RunnableFuture接口是Runnable和Future两个接口的简单封装。

因为实现了Runnable,所以有run方法,调用FutureTask.run()

因为实现了Future,所以有:

public abstract class SwingWorker<T, V> implements RunnableFuture<T> {
//FutureTask
private final FutureTask<T> future;
public final void run() {
future.run();
} public SwingWorker() {
Callable<T> callable =
new Callable<T>() {
//1. 任务线程一创建就处于PENDING状态
public T call() throws Exception {
//2. 当doInBackground方法开始时,任务线程就进入STARTED状态
setState(StateValue.STARTED);
return doInBackground();
}
}; //FutureTask
future = new FutureTask<T>(callable) { @Override
protected void done() {
doneEDT();
//3. 当doInBackground方法完成后,任务线程就处于DONE状态
setState(StateValue.DONE);
}
}; state = StateValue.PENDING;
propertyChangeSupport = new SwingWorkerPropertyChangeSupport(this);
doProcess = null;
doNotifyProgressChange = null;
} }

SwingWorker有两个类型参数:T及VT是doInBackground和get方法的返回类型;V是publish和process方法要处理的数据类型

SwingWorker实例不可复用,每次执行任务必须生成新的实例。

doInBackground和get和done

//1、doInBackground
protected abstract T doInBackground() throws Exception ;
//2、get --不可重写
public final T get() throws InterruptedException, ExecutionException {
return future.get();
}
//3、done
protected void done() {
} /**
* Invokes {@code done} on the EDT.
*/
private void doneEDT() {
Runnable doDone =
new Runnable() {
public void run() {
done();
}
};
//SwingWorker在EDT上激活done()
if (SwingUtilities.isEventDispatchThread()) {
doDone.run();
} else {
doSubmit.add(doDone);
}
}

doInBackground方法作为任务线程的一部分执行,它负责完成线程的基本任务,并以返回值来作为线程的执行结果。继承类须覆盖该方法并确保包含或代理任务线程的基本任务。不要直接调用该方法,应使用任务对象的execute方法来调度执行。

在获得执行结果后应使用SwingWorker的get方法获取doInBackground方法的结果。可以在EDT上调用get方法,但该方法将一直处于阻塞状态,直到任务线程完成。最好只有在知道结果时才调用get方法,这样用户便不用等待。为防止阻塞,可以使用isDone方法来检验doInBackground是否完成。另外调用方法get(longtimeout, TimeUnitunit)将会一直阻塞直到任务线程结束或超时。get获取任务结果的最好地方是在done方法内

在doInBackground方法完成之后,SwingWorker调用done方法。如果任务需要在完成后使用线程结果更新GUI组件或者做些清理工作,可覆盖done方法来完成它们。这儿是调用get方法的最好地方,因为此时已知道线程任务完成了,SwingWorker在EDT上激活done方法,因此可以在此方法内安全地和任何GUI组件交互

SwingWorker testWorker = new SwingWorker<Icon , Void>(){
@Override
protected Icon doInBackground() throws Exception {
Icon icon = retrieveImage(strImageUrl);
return icon;
} protected void done(){
//没有必要用invokeLater!因为done()本身是在EDT中执行的
SwingUtilities.invokeLater(new Runnable(){
@Override
public void run() {
Icon icon= get();
lblImage.setIcon(icon); //lblImage可通过构造函数传入
}
}
//execute方法是异步执行,它立即返回到调用者。在execute方法执行后,EDT立即继续执行
testWorker.execute();
  • 指定Icon作为doInBackground和get方法的返回类型
  • 因为并不产生任何中间数据,所以指定Void类型作为中间结果类型。

publish和process

SwingWorker在doInBackground方法结束后才产生最后结果,但任务线程也可以产生和公布中间数据。有时没必要等到线程完成就可以获得中间结果。

中间结果是任务线程在产生最后结果之前就能产生的数据。当任务线程执行时,它可以发布类型为V的中间结果,通过覆盖process方法来处理中间结果。

任务对象的父类会在EDT线程上激活process方法,因此在process方法中程序可以安全的更新UI组件

//SwingWorker.publish
protected final void publish(V... chunks) {
synchronized (this) {
if (doProcess == null) {
doProcess = new AccumulativeRunnable<V>() {
@Override
public void run(List<V> args) {
//调用process
process(args);
}
@Override
protected void submit() {
doSubmit.add(this);
}
};
}
}
doProcess.add(chunks);
} //SwingWorker.process 在EDT中调用
protected void process(List<V> chunks) {
}

当从任务线程调用publish方法时,SwingWorker类调度process方法。有意思的是process方法是在EDT上面执行,这意味着可以同Swing组件和其模型直接交互。

例如可让publish处理Icon类型的数据;则doInBackground对应应该返回List<Icon>类型

使用publish方法来发布要处理的中间数据,当ImageSearcher线程下载缩略图时,它会随着下载而更新图片信息列表,还会发布每一批图像信息,以便UI能在图片数据到达时显示这些图片。

如果SwingWorker通过publish发布了一些数据,那么也应该实现process方法来处理这些中间结果,任务对象的父类会在EDT线程上激活process方法,因此在此方法中程序可以安全的更新UI组件。

private void retrieveAndProcessThumbnails(List<ImageInfo> infoList) {
for (int x=0; x <infoList.size() && !isCancelled(); ++x) {
ImageInfo info = infoList.get(x);
String strImageUrl = String.format("%s/%s/%s_%s_s.jpg",
IMAGE_URL, info.getServer(), info.getId(), info.getSecret());
Icon thumbNail = retrieveThumbNail(strImageUrl);
info.setThumbnail(thumbNail);
//发布中间结果
publish(info);
setProgress(100 * (x+1)/infoList.size());
}
}
/**
* Process is called as a result of this worker thread's calling the
* publish method. This method runs on the event dispatch thread.
*
* As image thumbnails are retrieved, the worker adds them to the
* list model.
*
*/
@Override
protected void process(List<ImageInfo> infoList) {
for(ImageInfo info: infoList) {
if (isCancelled()) { //见下节
break;
}
//处理中间结果
model.addElement(info);
}
}

cancel和isCancelled

public final boolean cancel(boolean mayInterruptIfRunning) {
return future.cancel(mayInterruptIfRunning);
} /**
* {@inheritDoc}
*/
public final boolean isCancelled() {
return future.isCancelled();
}

如果想允许程序用户取消任务,实现代码要在SwingWorker子类中周期性地检查取消请求。调用isCancelled方法来检查是否有取消请求。检查的时机主要是:

  • doInBackground方法的子任务在获取每个缩略图之前
  • process方法中在更新GUI列表模型之前
  • done方法中在更新GUI列表模型最终结果之前

【例】判断是否被取消(见上例)

【例】取消

可以通过调用其cancel方法取消SwingWorker线程

private void searchImages(String strSearchText, int page) {  

  if (searcher != null && !searcher.isDone()) {
// Cancel current search to begin a new one.
// You want only one image search at a time.
//检查现有线程是否正在运行,如果正在运行则调用cancel来取消
searcher.cancel(true);
searcher = null;
}
...
// Provide the list model so that the ImageSearcher can publish
// images to the list immediately as they are available.
searcher = new ImageSearcher(listModel, API_KEY, strEncodedText, page);
searcher.addPropertyChangeListener(listenerMatchedImages);
progressMatchedImages.setIndeterminate(true);
// Start the search!
searcher.execute();
// This event thread continues immediately here without blocking.
}

setProgress和getProgress

任务对象有一个进度属性,随着任务进展时,可以将这个属性从0更新到100标识任务进度。当你在任务实例内处理这些信息时,你可以调用setProgress方法来更新这个属性。

当该属性发生变化时,任务通知处理器进行处理。(?)

//javax.imageio.ImageReader reader  

reader.addIIOReadProgressListener(new IIOReadProgressListener() {
...
public void imageProgress(ImageReader source, float percentageDone) {
setProgress((int) percentageDone);
}
public void imageComplete(ImageReader source) {
setProgress(100);
}
});

客户端调用1、更新进度条事件处理

/**
* ProgressListener listens to "progress" property changes
in the SwingWorkers that search and load images.
*/
class ProgressListener implements PropertyChangeListener {
// Prevent creation without providing a progress bar.
private ProgressListener() {}
ProgressListener(JProgressBar progressBar) {
this.progressBar = progressBar;
this.progressBar.setValue(0);
}
public void propertyChange(PropertyChangeEvent evt) {
String strPropertyName = evt.getPropertyName();
if ("progress".equals(strPropertyName)) {
progressBar.setIndeterminate(false);
int progress = (Integer)evt.getNewValue();
progressBar.setValue(progress);
}
}
private JProgressBar progressBar;
}

客户端调用2、添加监听

private void listImagesValueChanged(ListSelectionEvent evt) {
...
ImageInfo info = (ImageInfo) listImages.getSelectedValue();
String id = info.getId();
String server = info.getServer();
String secret = info.getSecret();
// No need to search an invalid thumbnail image
if (id == null || server == null || secret == null) {
return;
}
String strImageUrl = String.format(IMAGE_URL_FORMAT, server, id, secret);
retrieveImage(strImageUrl);
...
}
private void retrieveImage(String imageUrl) {
// SwingWorker,不可复用
ImageRetriever imgRetriever = new ImageRetriever(lblImage, imageUrl);
progressSelectedImage.setValue(0);
// Listen for changes in the "progress" property.
// You can reuse the listener even though the worker thread will be a new SwingWorker.
imgRetriever.addPropertyChangeListener(listenerSelectedImage);
progressSelectedImage.setIndeterminate(true);
// Tell the worker thread to begin with this asynchronous method.
imgRetriever.execute();
// This event thread continues immediately here without blocking.
}

SwingWorker的更多相关文章

  1. 使用泛型SwingWorker与EDT事件分发线程保持通讯

    为什么要使用SwingWorker 在swing开发中,如果一个应用程序,执行一些任务,需要大量的时间来完成,比如下载一个大文件或执行一个复杂的数据库查询. 我们假设这些任务是由用户使用一个按钮触发的 ...

  2. 【Java线程】SwingWorker的用法

    Swing应用程序员常见的错误是误用Swing事件调度线程(Event DispatchThread,EDT).他们要么从非UI线程访问UI组件:要么不考虑事件执行顺序:要么不使用独立任务线程而在ED ...

  3. 使用SwingWorker为界面执行异步任务

    ------------------siwuxie095                             工程名:TestSwingWorker 包名:com.siwuxie095.swing ...

  4. java Future 接口介绍

    (转自:http://blog.csdn.net/yangyan19870319/article/details/6093481) 在Java中,如果需要设定代码执行的最长时间,即超时,可以用Java ...

  5. [转]关于Android系统的”点九”

    李华明Himi原创,转载务必在明显处注明:转载自[黑米GameDev街区] 原文链接: http://www.himigame.com/android-game/321.html 前几天群成员讨论过关 ...

  6. 制作自定义背景Button按钮、自定义形状Button的全攻略(转)

    在Android开发应用中,默认的Button是由系统渲染和管理大小的.而我们看到的成功的移动应用,都是有着酷炫的外观和使用体验的.因此,我们在开发产品的时候,需要对默认按钮进行美化.在本篇里,笔者结 ...

  7. 【Swing】理解Swing中的事件与线程

    talk is cheap , show me the code. Swing中的事件 事件驱动 所有的GUI程序都是事件驱动的.Swing当然也是. GUI程序不同于Command Line程序,一 ...

  8. 11-Java 界面设计

    (一)Java界面设计概述 1.Java 界面设计的用途 2.AWT 简介 (1)Abstract Windows Toolkit 是最原始的工具包. 3.Swing 简介 4.SWT 简介 5.如何 ...

  9. ZipInputStream的用法

    package com.example.io; import java.awt.BorderLayout; import java.awt.EventQueue; import java.awt.ev ...

随机推荐

  1. [漏洞分析]thinkphp 5.x全版本任意代码执行分析全记录

    0x00 简介 2018年12月10日中午,thinkphp官方公众号发布了一个更新通知,包含了一个5.x系列所有版本存在被getshell的高风险漏洞. 吃完饭回来看到这个公告都傻眼了,整个tp5系 ...

  2. 多臂机测试, AB测试

    bandit  强盗,土匪:恶棍:敲诈者 ['bændɪt] 多臂机 multi-armed bandit MAB  简写. one-arm bandit   tiger ji 是一种自动AB测试的方 ...

  3. win10 设置

    韩梦飞沙  韩亚飞  313134555@qq.com  yue31313  han_meng_fei_sha ============

  4. BZOJ.3620.似乎在梦中见过的样子(KMP)

    题目链接 /* 896kb 6816ms A+B+A是KMP的形式,于是固定左端点,对于每个位置i,若fail[i]所能到的点k中(k=fail[fail[fail[...]]]),有满足len(l~ ...

  5. 潭州课堂25班:Ph201805201 第八课:函数基础和函数参数 (课堂笔记)

    1, 函数定义 def fun(): print('测试函数') fun() #调用函数 return 运行函数返回值 def fun(): name = [1,3,4,5] return name[ ...

  6. 潭州课堂25班:Ph201805201 第十二课 new方法,定制属性访问,描述符与装饰器 (课堂笔记)

    1,new方法: 类每次实例化时都会创建一个新的对象, class Textcls: # cls 是指类本身, def __new__(cls, *args, **kwargs): # 在 __ini ...

  7. php传入对象时获得类型提示

    类的类型提示 - 将类名放在需要约束的方法参数之前 语法格式: public function write(ShopProduct $shopProduct){} 数组提示: public funct ...

  8. getOutputStream() has already been called for this response解释以及解决方法

    异常:getOutputStream() has already been called for this response 的解决方法 今天在第一次接触使用“验证码”功能时,在执行时出现了异常信息: ...

  9. spyder 安装

    https://www.cnblogs.com/pcat/p/5398997.html

  10. R12.1.3 & R12.2.X 注册客户化应用

    R12.2 官方出了运行补丁脚本就可以自动创建客户化应用,所以也把此补丁应用在 R12.1.3上验证一下其可行性,做出以下实验. 1.创建客户化应用账号CUX --login user system ...