How to Use a Schedule(如何使用计划任务)
Schedule和ScheduleBase类的API文档是Schedule对象的详细源,在整合本文和示例源码过程中应该使用。
和Swarm工具包一样,Schedule对象负责所有的Repast仿真的状态的改变。因此,任何在仿真中你想要的改变必须在Schedule对象中预先计划。
What is Scheduled
在它的核心计划中包含确定的时间创建对象调用方法。然而这些方法调用必须被BasicAction类的子类覆盖。一个BasicAction包含一些Scheduler使用的变量和一些抽象方法public void execute()。任何一个BasicAction类的子类必须实现这个方法。这个方法中,预订计划的方法调用会发生。你可以自己写一个子类,或者让repast创建一个BasciAction实例。
因此,例如,你想要预先计划的是调用step()方法当Agent对象调用agent时,你可以写一个BasicAction子类在execute()方法中调用agent.step(),或者也可以让Repast创建BasicAction实例。前面的例子,你的类看起来应该和下边的类似:
.NET:
class MyAction : BasicAction { public override void execute() { agent.step(); } }
JAVA:
class MyAction extends BasicAction { public void execute() { agent.step(); } }
正是execute方法被调度者在调度时间调用。创建一个调度之后需要包含创建BasicActions并且计划这些BasicActions什么时间执行。
如果你希望Repast为你创建BasicActions,这是通过Schedule对象自己的方法调用的。通过Schedule对象创建BasicActions允许通过一个方法调用计划并且创建BasicAction。Repast1.3执行这些schedule创建的BasicActions的速度比手动创建的快。用schedule创建的BasicAction代价是它忽略了通常的编译检查。然而当模型运行的时候任何的错误都需要捕获。因此例如,如果你希望调度一个step()方法,你写了自己的BasicAction子类,但是将step()错误的拼写成stwp()。在你的模型运行之前,编译器将捕获这个错误。如果你让schedule对象来创建BasicAction,那么这个拼写错误直到模型真正运行起来了也不会捕获。不管你怎么创建你关注的BasicActions,示例仿真源代码用这两种方法的很好的例子。
对于JAVA来说,手工编写一个子类,一个BasicAction通常在你的模型类自己内部作为内部类。例如,HeatBugsModel.java buildSchedule()方法应该像下边那样编码,对于.NET一般继承完用的时候实例化就可以了。
.NET:
class HeatBugsRunner : BasicAction { public override void execute() { space.diffuse(); for (int i = 0; i < heatBugList.size[i]; bug.step(); } space.update(); dsurf.updateDisplay(); } } class SnapshotRunner : BasicAction { public override void execute() { dsurf.takeSnapshot(); } } private void buildSchedule() { HeatBugsRunner run = new HeatBugsRunner(); schedule.scheduleActionBeginning(1, run); schedule.scheduleActionAtInterval(100, new SnapshotRunner(), Schedule.LAST); }
JAVA:
private void buildSchedule() { class HeatBugsRunner extends BasicAction { public void execute() { space.diffuse(); for (int i = 0; i < heatBugList.size(); i++) { HeatBug bug = (HeatBug)heatBugList.get(i); bug.step(); } space.update(); dsurf.updateDisplay(); } }; class SnapshotRunner extends BasicAction { public void execute() { dsurf.takeSnapshot(); } }; HeatBugsRunner run = new HeatBugsRunner(); schedule.scheduleActionBeginning(1, run); schedule.scheduleActionAtInterval(100, new SnapshotRunner(), Schedule.LAST); }
这里有两个内部类HeatBugsRunner和SnapshotRunne,它们都继承了BasicAction。在这两个类中execute方法执行了属性的调用。实际上调动这些来执行其实就是简单的实现了BasicActions(HeatBugsRunner和SnapshotRunner)的实例化过程和调度这些实例化的类来执行。上面最后两行(schedule.s-cheduleActionBeginning和schedule.scheduleActionAtInterval的调用)恰恰做了这部分工作。(更多地在下边)所有的这些也可以通过匿名内部类来实现,但是可能会使代码清晰度下降。,BasicAction类是在scheudle上调度执行的,这个scheudle是是一个在模型setup()方法中创建的schedule对象。Schedule.LAST参数将在下边解释。
如果schedule自己创建BasicActions上边的代码应该这样写:
.NET:
public override void step() { space.diffuse(); for (int i = 0; i < heatBugList.Count; i++) { HeatBug bug = (HeatBug)heatBugList[i]; bug.step(); } space.update(); dsurf.updateDisplay(); } private void buildSchedule() { schedule.scheduleActionBeginning(1, this, "step"); schedule.scheduleActionAtInterval(100, dsurf, "takeSnapshot", Schedule.LAST); }
JAVA:
public void step() { space.diffuse(); for (int i = 0; i < heatBugList.size(); i++) { HeatBug bug = (HeatBug)heatBugList.get(i); bug.step(); } space.update(); dsurf.updateDisplay(); } private void buildSchedule() { schedule.scheduleActionBeginning(1, this, "step"); schedule.scheduleActionAtInterval(100, dsurf, "takeSnapshot", Schedule.LAST); }
在buildSchedule()中的第一行,调度了step方法,这里的“this”关键字引用了当前对象,这种情况下,模型在开始第一个时间单位后执行每一个时间单位。如果你需要在上边step方法中按照特殊的顺序执行不同的方法调用,或者在上边的HeatBugsRunner类,对他来说,将它们放到一个方法中是很方便的,而不是单独的调度每一个。
How to Schedule BasicActions
调度BasicActions执行时通过Schedule对象实现的。它允许你去调度actions在每个特定的时间片开始的时候发生,比如在某个特定的时间片,交替的时间片之间,在仿真过程的暂停时间点,和仿真过程的结束(这里的tick对于所有的BasicActions是一个反复的执行过程)。了解ticks仅仅是一种调度Actions和它们自身的关系的一种方式而不是隔离这些实体它们自身。在第3个tick上调度的Actions在第10个tick开始执行之前将完全执行。但是如果在tick3和tick10之间没有任何的Actions调度,则第10个tick上的actions在第3个tick上的action执行完之后立即执行。这种情况下,第4个和第9个ticks不存在。Ticks也可能是小数值。例如,有可能会在tick1.25c处调动一个action。
对于想调度的什么,对每个agent都有一个叫做“step”的方法,显示界面的updateDisplay方法通常是每一个tick都调度。上边的例子调度的HeatBugsRunner的执行execute()方法从第一个tick开始每一个tick都发生,SnapshotRunner的执行execute()方法每100ticks执行一次。
你能够在其他调度的BasicActions利用Schedule.LAST参数时,确保确定的BasicActions发生,当调度的BasicAction执行时。这个参数被schedule.scheduleActionAt, schedule.scheduleActionAtInterval方法接受。例如:
schedule.scheduleActionAtInterval(100, dsurf, "takeSnapshot", Schedule.LAST);
调度了一个每100ticks的takeSnapshot方法的执行。并且确保了在任何其它的没有调度Schedule.LAST参数的actions执行后执行。这特别的有意义,在实际的设计中对于数据收集,
快
照,电影画面制作能在BasicActions改变数据源或图像后调度执行。
没有用Schedule.LAST调度的BasicActions将随机执行或者在任何设定了Schedule.LAST参数的BasicActions之前执行。如果你希望顺序的调度那些non-Schedule.LAST BasicActions
,你可以整合你的BasicActions通过放它们到一个方法中或者创建一个顺序类型的ActionGroup并且添加你的BasicActions到这个ActionGroup中。这个ActionGroup应该通过schedule
对象调度。例如:
.NET:
class Runner2 : BasicAction { public override void execute() { doSomethingElse(); } }; schedule.scheduleActionBeginning(1, obj1, "doSomething"); schedule.scheduleActionBeginning(1, new Runner2()); schedule.scheduleActionAtInterval(3, obj3, "takePicture");
JAVA:
class Runner2 extends BasicAction { public void execute() { doSomethingElse(); } }; schedule.scheduleActionBeginning(1, obj1, "doSomething"); schedule.scheduleActionBeginning(1, new Runner2()); schedule.scheduleActionAtInterval(3, obj3, "takePicture");
这里的三个BasicActions将在执行的时间片内随机顺序执行。所以假设doSomething()打印出“doSomething”,doSomethingElse() 和 takePicture()同理,6次迭代次数输出将可能如下:
tick1: doSomething, doSomethingElse tick2: doSomethingElse, doSomething tick3: takePicture, doSomethingElse, doSomething tick4: doSomethingElse, doSomething tick5: doSomething, doSomethingElse tick6: doSomethingElse, takePicture, doSomething
如果我们将schedule.scheduleActionAtInterval(3, obj3, "takePicture");改成schedule.scheduleActionAtInterval(3, obj3, "takePicture", Schedule.
-LAST);
“takePicture”将在第3个时间片和第6个时间片发生。
为了使doSomething和doSomethingElse以顺序方式执行,我们可以按照下边的两种方法之一来做:
调用这两个方法在另外的一个方法中
public void doIt() {
doSomething();
doSomethingElse();
}
doIt是obj1对象的一个方法。
.NET:
class Runner3 : BasicAction { public override void execute() { takePicture(); } }; schedule.scheduleActionBeginning(1, obj1, doIt); schedule.scheduleActionAtInterval(3, new Runner3());
JAVA:
class Runner3 extends BasicAction { public void execute() { takePicture(); } }; schedule.scheduleActionBeginning(1, obj1, doIt); schedule.scheduleActionAtInterval(3, new Runner3());
或者我们能创建一个顺序的ActionGroup,并向组内添加Runner1和Runner2,并且调度ActionGroup执行。
.NET:
class Runner1 : BasicAction { public override void execute() { doSomething(); } }; class Runner2 : BasicAction { public override void execute() { doSomethingElse(); } }; class Runner3 : BasicAction { public override void execute() { takePicture(); } }; ActionGroup group = new ActionGroup(ActionGroup.SEQUENTIAL); group.addAction(new Runner1()); group.addAction(new Runner2()); schedule.scheduleActionBeginning(1, group); schedule.scheduleActionAtInterval(3, new Runner3());
JAVA:
class Runner1 extends BasicAction { public void execute() { doSomething(); } }; class Runner2 extends BasicAction { public void execute() { doSomethingElse(); } }; class Runner3 extends BasicAction { public void execute() { takePicture(); } }; ActionGroup group = new ActionGroup(ActionGroup.SEQUENTIAL); group.addAction(new Runner1()); group.addAction(new Runner2()); schedule.scheduleActionBeginning(1, group); schedule.scheduleActionAtInterval(3, new Runner3());
BasicActions(Runner1和Runner2)将以它们在ActionGroup添加的顺序执行。需要注意的是我们也可以利用ActionGroup的createAction*系列的方法在一个步骤中创建添加BasicActions。
调度机制也为实际线程后台行为提供执行。为了创建这样的action,一些BasicAction就要像之前添加一个参数来定义action的duration一样被调度执行。duration指定这样的action在后台执行多长时间。例如,如果action A 在tick3被调度执行,并且持续4个tick,它将在第3个tick开始执行,然后在这4个tick期间继续执行,任何被调度的actions同时执行。一旦4个ticks过去了,执行引擎继续调用action A的执行方法,直到action A已经完成了执行。这里需要重点说明的是action A 在ticks期间不是重复的执行。而是执行机制调用A的执行方法,而不是等待它完成,因为它通常会当A的执行方法继续在后台执行立即执行下一个调度action。仅当很多的与duration相等ticks过去后,执行机制停下来并且等待action A去完成。
一个带有duration的BasicAction的调度例子:
schedule.scheduleActionAt(3, new LongRunningAction(), 4);
这里这个新的LongRunningAction()方法是某个人定义的BasicAction,假设它完成执行需要很长时间,且不依赖其它actions的结果。这个action将在第3个tick并且在后台持续的执行知道第7(3+4)个tick,在第3个tick时,执行机制会等待它完成。
这里值得一提的是用线程(也就是说你做的事情是持续时间的actions)编程是棘手的。action应该是和其它的model无关的独立的复杂计算过程。至少,它不更新显示或者任何可视化的界面,至少在它完成之前。这样的线程actions的使用在批处理环境中比较容易,但是如果足够小心处理的话,它也可以在gui环境中使用。
从Schedules或者ActionGroups中也可以去掉BasicActions。关于这些对象以及scheduling的更多信息,请看documentation API。