Dorado 7 : 长任务(SEUG)

长任务(LongTask)功能是在构建在 长连接(SEUG) 基础之上的。用于完成那些需要在后台执行较长时间的任务,这些任务的执行时长可以是几十秒、几分钟、几小时、甚至更长,对于这种很长的任务用户几乎不可能一直在网页端等待他们执行结束,但是他们往往又需要随时了解该任务的执行状况 ,或者对正在执行的任务进行一些调度管理。长任务(LongTask)功能正是为了解决用户的上述困扰而提供的。

LongTask的Client端

使用LongTask的方法与使用AjaxAction的方法有些类似,在IDE工具栏的Action中可以找到LongTask这样一个组件,它有如下几个重要的属性...

  • taskName - 该属性的含义与AjaxAction中的service属性是一致的,表示一个后台长任务的服务名称。
  • appearence - 用于指定系统如何向用户展示当前正在执行任务的信息。其中mainTask表示显示一个模态的提示器;daemonTask表示显示一个非模态的提示器;none表示不现实任何提示,此时用户可以利用LongTask的事件自行确定如何展示任务的执行状态。
  • disableOnActive - 表示是否要在检测到有正在执行的任务时自动禁用本控件。

作为一个最简单的例子,我们可以直接在LongTask的taskName中定义一个字符串,如 #simpleTask,根据Dorado7.3.2中开始提供的新特性,此种简略写法实际表示的值是:<小写字符开头的View名称>#simpleTask。假设我们的View的文件名是TestLongTask.view.xml,那么#simpleTask实际表示testLongTask#simpleTask。(此规则同样适用于AjaxAction.service,DataSet.dataProvider,UpdateAction.dataResolver等属性)

LongTask的Server端

按照默认的开发讨论,我们接下来可以直接在一个名为TestLongTask的JavaBean编写LongTask后端逻辑代码了。例如...

@Component
public class TestLongTask {

	@Expose
	public LongTask simpleTask() {
		return new LongTask() {
			public Object call() throws Exception {
				Thread.sleep(5000);
				return null;
			}
		};
	}
}

从上面的代码可见,定义的LongTask后端的方法与Ajax后端的方法非常相似,唯一的要求是此方法必须返回一个LongTask对象。LongTask是java.util.concurrent.Callable<Object>的实现类,会由LongTask的调度器在另一个线程中执行。在最为简单的示例中,我们只需要实现LongTask的call()方法,就已经完成了一个长任务。

LongTask类中有这样几个常用的方法...

  • setStateInfo() - 每次设置的TaskStateInfo对象包含三部分的信息——state、text和data。其中state是enum类型的状态代码,而text和data是当前状态附带的信息,您可以根据自己的需要给text和data设置任意的值。目前Dorado支持的任务状态有....
    • waiting - 等待,例如当任务调度器发现目前正在执行某个任务的实例已经达到了上限,而用户仍希望再开启新的长任务,那么新的长任务将会进入等待队列,即等待状态。
    • running - 正在执行。
    • suspending - 正在尝试挂起。
    • suspended - 挂起。
    • resuming - 正在尝试恢复执行。
    • terminated - 执行结束。
    • aborting - 正在中止,即取消执行。
    • aborted - 已终止,即已取消。
    • error - 出错并以退出。
  • appendLog() - 向客户端输出日志信息,通常是TaskLog对象封装的一段文本。

LongTask的StateInfo是可以重复设置的,比如同样的是处于running状态,我们可以先设置为new TaskStateInfo(TaskState.running, "正在复制第1个文件"),然后再设置为new TaskStateInfo(TaskState.running, "正在复制第2个文件")。

那么既然StateInfo是可以重复设置的,它和Log有什么区别呢?

区别在于StateInfo是一种可以维持的信息,直到我们设置下一个StateInfo之前,当前的StateInfo对于长任务而言都是有效的。而Log则瞬间的,更像是一种调试信息。

举个例子,当一个长任务正在执行时我们刷新了网页,那么当网页刷新完成后Dorado会自动的将该任务的StateInfo同步到Client端,用户可以立即看到目前有一个长任务正在执行,并且它的状态是是“running-正在复制第2个文件”。而对于刷新网页期间长任务输出的Log信息,用户是无法接收到放任,他只能看到从此刻开始的Log信息。

LongTask的调度

在声明一个LongTask的方法时,我们还可以利用@TaskScheduler来为长任务配置简单的调度信息。@TaskScheduler包含以下几个属性...

  • scope - 任务的可见范围,目前包含两种取值 session(默认)和application。
  • maxRunning - 最大可运行实例数。
  • maxWaiting - 最大等待队列的长度。
  • impl - 自定义任务调度器的实现类。(待补充...)

对于某个长任务而言,其在scope指定的范围内仅允许存在一个正在运行的实例(包括正在执行的和正在等待执行的)。因此当scope为session时,maxRunning和maxWaiting所表示的是该长任务在整个系统中的运行数和等待数,而不是指当前Session中的。而当scope为application时,maxRunning和maxWaiting将是无效的,因为此时整个系统中事实上只允许存在一个正在运行的实例(包括正在执行的和正在等待执行的)。

实时控制

在LongTask执行的过程中,我们可以通过客户端LongTask的suspend()、resume()、abort()等方法来暂停、恢复或中止LongTask的执行。不过这三个方法都不能自动的完成所有的工作,通常用户都需要编写一定的代码才能真正的实现对LongTask的执行控制。

以abort()操作为例,当用户在Client端调用了abort()方法后,Server端的LongTask对象会自动进入aborting状态,但是LongTask并不会自动的停下来,它仍会以正常的方式继续执行。因为我们不能强行的杀死LongTask目前的执行线程,这种做法是不受推荐且存在隐患的。开发者需要自行判断aborting状态,并且决定以何种方式让LongTask优雅的退出执行。这听起来有些麻烦,不过好在在绝大部分情况下我们并不需要去控制LongTask的执行过程。

下面是一个支持suspend()、resume()、abort()这三种操作的LongTask的代码实例,可以从这里获得一些启发...

@Expose
public LongTask copyFiles() {
	return new LongTask() {
		public Object call() throws Exception {
			for (File file: files) {
				synchronized (this) {
					if (isSuspendRequired()) {
						setStateInfo(new TaskStateInfo(TaskState.suspended);
						wait();
					}
					if (isAbortRequired()) {
						setStateInfo(new TaskStateInfo(TaskState.aborted));
						break;
					}
					FileUtils.copy(file, destDir);
				}
			}
			return null;
		}
		protected void doResume() {
			notify();
			setStateInfo(new TaskStateInfo(TaskState.running));
		}
		protected void doAbort() {
			notify();
		}
	};
}