使用Swing Worker线程 --执行后台任务的新方法

原创 2004年08月29日 23:21:00

本文给出了一些使用SwingWorker类的例子。SwingWorker类的目的是实现一个后台线程,让你可以用它来执行一些费时的操作,而不影响你的程序的GUI的性能。关于SwingWorker类的一些基本信息,请参阅《线程和Swing》。

对执行一些费时或可阻塞操作的Swing程序来说,线程是基本的解决之道。例如,如果你的应用程序要根据用户选择的菜单项发出一个数据库请求或加载一个文件,那么你应该在一个单独的线程中完成这些工作。本文阐述了在一个分离的worker线程中完成上述工作的途径。
本文包括以下主要内容:

  • SwingWorker类:这一部分告诉你怎样下载SwingWorker类并描述了SwingWorker类的用途。介绍了SwingWorker类的interrupt()方法。
  • 引入Worker线程的例子:演示一个运用SwingWorker类的应用程序。
  • 例一:中断一个Swing Worker线程:解释如何运用interrupt()方法来中断worker线程。
  • 例二:从Swing Worker线程反馈给用户:例一的增强,添加了一个模式对话框以提示用户输入。



 

概览:SwingWorker类


因为SwingWorker类并不是Java发行版的一部分,你需要下载和编译它才能使用。它的源代码在这里:
SwingWorker.java

SwingWorker类简介


SwingWorker类可以简单且方便地用于在一个新的线程中计算一个数值。要使用这个类,你只要创建一个SwingWorker的子类并覆盖SwingWorker.construct()方法来执行计算。然后你实例化它,并在这个新实例上调用start()方法。
例如,下面的代码片断中产生了一个线程,其中构造了一个字符串。然后,片断中使用了get()方法来取得前面由construct方法所返回的值,并且在必要时将等待。最后,在显示器上显示出字符串的值。

SwingWorker worker = new SwingWorker() {
   public Object construct() {
      return "Hello" + " " + "World";
   }
};
worker.start();
System.out.println(worker.get().toString());

在实际的应用程序中,construct方法会做些更有用(但可能很费时)的事情。比如,它可能做下列工作之一:

  • 执行大量的运算
  • 执行可能导致大量的类被装载的代码
  • 为网络或磁盘I/O阻塞
  • 等待其他资源


在上面的代码片断中没有展示的一个SwingWorker类的特性是,当construct()返回后,SwingWorker可以让你在事件派发线程中执行一些代码。你可以通过覆盖SwingWorker的finished()方法来做到这一点。典型地,你可以用finished()来显示刚刚构造的一些组件或设置组件上显示的数据。
原始版本的SwingWorker类的一个局限是,一旦一个worker线程开始运行,你无法中断它。(译注:本文、本文的例子及SwingWorker类曾被更新。)不管怎么说,对于一个交互应用程序来说,让用户在工作线程完成前一直等待是相当糟糕的风格。如果用户希望终止一项正在执行中的操作,执行此操作的线程应该能够尽快中止。

使用interrupt()方法


在第二版的SwingWorker类中加入了一个interrupt()方法以允许中断一个线程。你的线程应该以下面两种途径之一得到中断的通知:

  • 正在执行诸如sleep()或wait()方法的线程在interrupt()被调用时会抛出一个InterruptedException。
  • 线程可以显式询问它是否已被中断,通过形如以下代码:
    Thread.interrupted()
     


使用sleep()或wait()方法的工作线程(如后面例子中的线程)一般不需要显式检查是否被中断。通常让sleep()或wait()方法抛出InterruptedException就足够了。
不过,如果你希望能够中断一个不含定时循环的SwingWorker,还是需要用interrupted()方法来显式检查中断。

引入Worker线程的例子


本文余下的部分讨论一个包含两个worker线程的例子的程序。下图是程序主窗口的截图,和它弹出的一个对话框:
frame.gif
dialog.gif
例子的源代码由以下文件组成:


你可以下载一个包含上面所有文件的zip文件。在此zip文件中还包含一个带有HTML格式的说明的例子,这个版本的例子更能说明问题(但也更复杂)。
运行此例子前要先编译:
/usr/local/java/jdk1.3.0/bin/javac ThreadsExample.java
用以下命令行运行此例子:
/usr/local/java/jdk1.3.0/bin/java ThreadsExample
当你按下“Start”按钮,相应例子的worker线程将被创建。你可以在进度条中查看它的进度。你可以按下“Cancel”按钮来中断worker线程。在启动例子二稍等几秒,你会看到一个对话框提示你确认是否执行。稍后我们再详述这个。

例子一:中断Swing Worker线程


接着我们要讨论的例子是Example1.java。这个例子中的worker线程包含一个执行100次的循环,并在两次循环之间睡眠半秒。
//progressBar maximum is NUMLOOPS (100) progressBar的最大值是NUMLOOPS (100)

for(int i = 0; i < NUMLOOPS; i++) {
   updateStatus(i);
   ...
   Thread.sleep(500);
}

为了向你展示如何使用interrupt()方法,我们的例子程序可以让你启动一个SwingWorker然后等待它完成或者中断它。程序中的SwingWorker子类的construct()方法所作的唯一一件事就是调用Example1的doWork()方法。doWork()方法的完整源码列在下面。这个例子在处理worker线程时会重置进度条并把标签设为“Interrupted”。因为中断可能发生在sleep()方法调用之外,所以代码中在调用sleep()方法之前要先检查中断与否。

Object doWork() {
   try {
      for(int i = 0; i < NUMLOOPS; i++) {
         updateStatus(i);
         if (Thread.interrupted()) {
            throw new InterruptedException();
         }
         Thread.sleep(500);
      }
   }
   catch (InterruptedException e) {
      updateStatus(0);
      return "Interrupted";  
   }
   return "All Done"; 
}

在此方法中执行的费时操作应该:周期性地让用户知道它有所进展。updateStatus()方法会为事件派发线程排队Runnable对象(记住:不要在其他线程中执行GUI工作)。一旦按下“Start”按钮,动作监听器(action listener)会创建SwingWorker,使得worker线程被创建。Worker线程启动后,它将执行它的construct()方法,该方法将调用doWork()(如下面的代码所示)。下面的代码例示了Example1实现的SwingWorker的子类。

worker = new SwingWorker() {
   public Object construct() {
      return doWork();
   }
   public void finished() {
      startButton.setEnabled(true);
      interruptButton.setEnabled(false);
      statusField.setText(get().toString());
   }
};

finished()方法在construct()方法返回后执行(即worker线程完成后)。它的任务只是简单地重新使“Start”按钮有效,同时使“Cancel”按钮无效,并将状态域显示的值设置成worker的计算结果。记住finished()方法是在事件派发线程中执行的,所以它可以安全地直接更新GUI。

例子二:从Worker线程提示用户


这个例子实现为Example1的子类。唯一的区别是,在worker线程执行了大约两秒后,它将阻塞直到用户响应一个Continue/Cancel模态对话框。如果用户选择的不是“Continue”,我们就退出doWork()循环。
这个例子演示了一个在许多worker线程中应用的惯用法:如果worker执行中到达一个不期望的状态,它将阻塞起来直到用户被提醒或用一个模态对话框从用户那里收集到了更多信息。这种做法有一点复杂,因为对话框的显示需要放到事件派发线程中,且worker线程需要被阻塞直到用户解除了该模式对话框。
我们使用SwingUtilities的invokeAndWait()方法来在事件派发线程中弹出对话框。与invokeLater()不同,invokeAndWait()会阻塞起来直到它的Runnable对象返回。在我们的例子中,Runnable对象直到对话框被解除才返回。我们创建一个内部Runnable类DoShowDialog,来完成弹出对话框。一个实例变量DoShowDialog.proceedConfirmed,被用来记录用户的选择:

class DoShowDialog implements Runnable {
   boolean proceedConfirmed;
   public void run() {
      Object[] options = {"Continue", "Cancel"};
         int n = JOptionPane.showOptionDialog
         (Example2.this,
         "Example2: Continue?",
         "Example2",
         JOptionPane.YES_NO_OPTION,
         OptionPane.QUESTION_MESSAGE,
            null,
            options,
            "Continue");
         proceedConfirmed =
            (n == JOptionPane.YES_OPTION);
   }
}

因为showConfirmDialog()方法弹出一个模态对话框,调用会阻塞直到用户解除该对话框。
为了显示对话框并阻塞调用线程(worker线程)直到对话框被解除,worker线程调用invokeAndWait()方法,定义一个DoShowDialog的实例:

DoShowDialog doShowDialog = new DoShowDialog();
try {
   SwingUtilities.invokeAndWait(doShowDialog);
}
catch 
   (java.lang.reflect.
      InvocationTargetException e) {
      e.printStackTrace();
}

代码中捕获的InvocationTargetException是调试DoShowDialog的run()方法的残留。当invokeAndWait()方法返回后,worker线程可以读取doShowDialog.proceedConfirmed来获得用户的响应。

java的后台任务调度@Schedule和@Async

Java提供了两种后台任务的方法 调度任务;@Schedule 异步任务;@Async 当然,使用这两个是有条件的,需要在spring应用的上下文中声明 当然,如果我们是基于java配置的,需要...
  • f112122
  • f112122
  • 2015年08月18日 00:22
  • 2163

Netty里面的Boss和Worker【Server篇】

转载地址:https://my.oschina.net/bieber/blog/406799   最近在总结Dubbo关于Netty通信方面的实现,于是也就借此机会深入体会了一下Netty。一般...
  • zhengyangzkr
  • zhengyangzkr
  • 2017年05月09日 21:53
  • 998

Java线程池执行器ThreadPoolExecutor工作原理

JDK版本:1.7.0_45。本文尝试分析ThreadPoolExecutor的构造方法的参数的作用,然后再分析主要逻辑。 一、构造方法及其参数 ThreadPoolExecutor位于java.ut...
  • tomatomas
  • tomatomas
  • 2016年04月12日 17:14
  • 2603

简单实用后台任务执行框架(Struts2+Spring+AJAX前端web界面可以获取进度)

使用场景: 在平常web开发过程中,有时操作员要做一个后台会运行很长时间的任务(如上传一个大文件到后台处理),而此时前台页面仍需要给用户及时的进度信息反馈,同时还要避免前台页面超时。 框架介绍: 本架...
  • u014569459
  • u014569459
  • 2014年05月24日 22:27
  • 1598

使用IntentService执行后台任务

使用IntetnService执行后台任务
  • mnizq
  • mnizq
  • 2016年11月30日 10:43
  • 115

PHP + PYTHON 多任务多线程,后台运行,计划任务-实现方法

有时候在做程序的时候,会发现AJAX来做批量异步不怎么好。而PHP又不支持多线程。效率不高。 在这个时候就会使用PYTHON来做后台多线程操作。 在WINDOWS下比较麻烦,第一:如果是多任务的话,需...
  • RuningBoys
  • RuningBoys
  • 2016年03月25日 09:42
  • 1120

Java线程池执行原理分析

原文地址:http://www.codeceo.com/article/java-thread-pool-implementation.html?ref=myread 本文将会围绕线程池的生...
  • tanga842428
  • tanga842428
  • 2016年11月14日 19:38
  • 413

Swing GUI线程安全问题

Swing提供一组“轻量级”(全部是 Java 语言)组件,尽量让这些组件在所有平台上的工作方式都相同。 是单线程、线程不安全的。 现象1:当点击了开始按钮,画面就卡住了。按钮不能点击,进度条没有...
  • bbirdsky
  • bbirdsky
  • 2013年11月30日 22:55
  • 1481

Swing界面响应与线程安全

主线程和UI线程 java程序的主线程 当java程序启动的时候,一个线程立刻执行,这个线程叫做main thread,执行main方法。 主线程的特征: 他是产生其他子线程的线程主线程中执行程...
  • bruno231
  • bruno231
  • 2014年12月23日 09:52
  • 4657

Swing Worker应用举例

原文 在开发Java Swing应用程序的过程中,有两个原则是必须要牢记的:     1.耗时的操作(例如从数据库查询大量数据,读取URI资源等)一定不能运行在EDT(事件派发线程)上,否则会导致S...
  • u010217750
  • u010217750
  • 2016年09月08日 16:57
  • 149
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:使用Swing Worker线程 --执行后台任务的新方法
举报原因:
原因补充:

(最多只允许输入30个字)