`
morgan-java
  • 浏览: 56425 次
  • 性别: Icon_minigender_1
  • 来自: 上海
文章分类
社区版块
存档分类
最新评论
阅读更多
你曾经需要应用执行一个任务吗?这个任务每天或每周星期二晚上11:30,或许仅仅每个月的最后一天执行。一个自动执行而无须干预的任务在执行过程中如果发生一个严重错误,应用能够知到其执行失败并尝试重新执行吗?你和你的团队是用java编程吗?如果这些问题中任何一个你回答是,那么你应该使用 Quartz调度器。
  
  旁注:Matrix目前就大量使用到了Quartz。比如,排名统计功能的实现,在Jmatrix里通过Quartz定义了一个定时调度作业,在每天凌晨一点,作业开始工作,重新统计大家的Karma和排名等。
  
  还有,RSS文件的生成,也是通过Quartz定义作业,每隔半个小时生成一次RSS XML文件。
  
  所以Quartz使用的地方很多,本文无疑是一篇很好的入门和进阶的文章,在此,感谢David w Johnson的努力!
  
  Quartz让作业调度简单
  
  Quartz是一个完全由java编写的开源作业调度框架。不要让作业调度这个术语吓着你。尽管Quartz框架整合了许多额外功能,但就其简易形式看,你会发现它易用得简直让人受不了!。简单地创建一个实现org.quartz.Job接口的java类。Job接口包含唯一的方法:
  
  public void execute(JobExecutionContext context)
  throws JobExecutionException;
  
  在你的Job接口实现类里面,添加一些逻辑到execute()方法。一旦你配置好Job实现类并设定好调度时间表,Quartz将密切注意剩余时间。当调度程序确定该是通知你的作业的时候,Quartz框架将调用你Job实现类(作业类)上的execute()方法并允许做它该做的事情。无需报告任何东西给调度器或调用任何特定的东西。仅仅执行任务和结束任务即可。如果配置你的作业在随后再次被调用,Quartz框架将在恰当的时间再次调用它。
  
  如果你使用了其它流行的开源框架象struts,你会对Quartz的设计和部件感到舒适。虽然两个开源工程是解决完全不同的问题,还是有很多相似的之处,就是开源软件用户每天感觉很舒适。Quartz能用在单机J2SE应用中,作为一个RMI服务器,也可以用在web应用中,甚至也可以用在J2EE应用服务器中。
  
  Quartz的发展史
  
  尽管Quartz今年开始受到人们注意,但还是暂时流行。Quartz由James House创建并最初于2001年春天被加入sourceforge工程。接下来的几年里,有许多新特征和版本出现,但是直到项目迁移到新的站点并成为 OpenSymphony项目家族的一员,才开始真正启动并受到应有的关注。
  
  James House仍然和几个协助他的业余开发者参与大量开发工作。Quartz开发团队今年能发布几个新版本,包括当前正处在候选发布阶段的1.5版。
  
  上手Quartz
  
  Quartz工程驻留在OpenSymphony站点上。在Quartz站点上可以找到许多有用的资源:JavaDocs,包含指南的文档,CVS访问,用户和开发者论坛的连接,当然也有下载。
  
  从下载连接取得Quartz的发布版本,并且解压到到本地目录。这个下载文件包含了一个预先构建好的Quartz二进制文件(quartz.jar),你可以将它放进自己的应用中。Quartz框架只需要少数的第三方库,并且这些三方库是必需的,你很可能已经在使用这些库了。
  
  你要把Quartz的安装目录的<quartz- install>/lib/core 和 <quartz-install>/lib/optional目录中的第三方库加进你自己的工程中。大多数第三方库是我们所熟知和喜欢的标准 Jakarta Commons库,像Commons Logging, Commons BeantUtils等等。
  
  quartz.properties文件
  
  Quartz有一个叫做quartz.properties的配置文件,它允许你修改框架运行时环境。缺省是使用Quartz.jar里面的 quartz.properties文件。当然,你应该创建一个quartz.properties文件的副本并且把它放入你工程的classes目录中以便类装载器找到它。quartz.properties样本文件如例1所示。
  
  例1.quartz.properties文件允许修改Quartz运行环境:
  
  #===============================================================
  # Configure Main Scheduler Properties
  #===============================================================
  org.quartz.scheduler.instanceName = QuartzSchedulerorg.quartz.scheduler.instanceId = AUTO
  #===============================================================
  # Configure ThreadPool
  #===============================================================
  org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPoolorg.quartz.threadPool.threadCount = 5org.quartz.threadPool.threadPriority = 5
  #===============================================================
  # Configure JobStore
  #===============================================================
  org.quartz.jobStore.misfireThreshold = 60000org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore
  
  一旦将Quartz.jar文件和第三方库加到自己的工程里面并且quartz.properties文件在工程的classes目录中,就可以创建作业了。然而,在做这之前,我们暂且回避一下先简短讨论一下Quartz架构。
  
  Quartz内部架构
  
  在规模方面,Quartz跟大多数开源框架类似。大约有300个java类和接口,并被组织到12个包中。这可以和Apache Struts把大约325个类和接口以及组织到11个包中相比。尽管规模几乎不会用来作为衡量框架质量的一个特性,但这里的关键是quarts内含很多功能,这些功能和特性集是否成为、或者应该成为评判一个开源或非开源框架质量的因素。
  
  Quartz调度器
  
  Quartz框架的核心是调度器。调度器负责管理Quartz应用运行时环境。调度器不是靠自己做所有的工作,而是依赖框架内一些非常重要的部件。 Quartz不仅仅是线程和线程管理。为确保可伸缩性,Quartz采用了基于多线程的架构。启动时,框架初始化一套worker线程,这套线程被调度器用来执行预定的作业。这就是Quartz怎样能并发运行多个作业的原理。Quartz依赖一套松耦合的线程池管理部件来管理线程环境。本片文障中,我们会多次提到线程池管理,但Quartz里面的每个对象是可配置的或者是可定制的。所以,例如,如果你想要插进自己线程池管理设施,我猜你一定能!
  
  作业
  
  用Quartz的行话讲,作业是一个执行任务的简单java类。任务可以是任何java代码。只需你实现org.quartz.Job接口并且在出现严重错误情况下抛出JobExecutionException异常即可。Job接口包含唯一的一个方法execute(),作业从这里开始执行。一旦实现了Job接口和execute()方法,当Quartz确定该是作业运行的时候,它将调用你的作业。Execute()方法内就完全是你要做的事情。下面有一些你要在作业里面做事情的例子:
  
  · 用JavaMail(或者用其他的像Commons Net一样的邮件框架)发送邮件
  · 创建远程接口并且调用在EJB上的方法
  · 获取Hibernate Session,查询和更新关系数据库里的数据
  · 使用OSWorkflow并且从作业调用一个工作流
  · 使用FTP和到处移动文件
  · 调用Ant构建脚本开始预定构建
  
  这种可能性是无穷的,正事这种无限可能性使得框架功能如此强大。Quartz给你提供了一个机制来建立具有不同粒度的、可重复的调度表,于是,你只需创建一个java类,这个类被调用而执行任务。
  
  作业管理和存储
  
  作业一旦被调度,调度器需要记住并且跟踪作业和它们的执行次数。如果你的作业是30分钟后或每30秒调用,这不是很有用。事实上,作业执行需要非常准确和即时调用在被调度作业上的execute()方法。Quartz通过一个称之为作业存储(JobStore)的概念来做作业存储和管理。
  
  有效作业存储
  
  Quartz提供两种基本作业存储类型。第一种类型叫做RAMJobStore,它利用通常的内存来持久化调度程序信息。这种作业存储类型最容易配置、构造和运行。对许多应用来说,这种作业存储已经足够了。然而,因为调度程序信息是存储在被分配给JVM的内存里面,所以,当应用程序停止运行时,所有调度信息将被丢失。如果你需要在重新启动之间持久化调度信息,则将需要第二种类型的作业存储。
  
  第二种类型的作业存储实际上提供两种不同的实现,但两种实现一般都称为JDBC作业存储。两种JDBC作业存储都需要JDBC驱动程序和后台数据库来持久化调度程序信息。这两种类型的不同在于你是否想要控制数据库事务或这释放控制给应用服务器例如BEA's WebLogic或Jboss。(这类似于J2EE领域中,Bean管理的事务和和容器管理事务之间的区别)
  
  这两种JDBC作业存储是:
  
  ·JobStoreTX:当你想要控制事务或工作在非应用服务器环境中是使用
  ·JobStoreCMT:当你工作在应用服务器环境中和想要容器控制事务时使用。
  
  JDBC作业存储为需要调度程序维护调度信息的用户而设计。
  
  作业和触发器
  
  Quartz设计者做了一个设计选择来从调度分离开作业。Quartz中的触发器用来告诉调度程序作业什么时候触发。框架提供了一把触发器类型,但两个最常用的是SimpleTrigger和CronTrigger。SimpleTrigger为需要简单打火调度而设计。典型地,如果你需要在给定的时间和重复次数或者两次打火之间等待的秒数打火一个作业,那么SimpleTrigger适合你。另一方面,如果你有许多复杂的作业调度,那么或许需要 CronTrigger。
  
  CronTrigger是基于Calendar-like调度的。当你需要在除星期六和星期天外的每天上午10点半执行作业时,那么应该使用CronTrigger。正如它的名字所暗示的那样,CronTrigger是基于Unix克隆表达式的。
  
  作为一个例子,下面的Quartz克隆表达式将在星期一到星期五的每天上午10点15分执行一个作业。
  
  0 15 10 ? * MON-FRI
  
  下面的表达式
  
  0 15 10 ? * 6L 2002-2005
  
  将在2002年到2005年的每个月的最后一个星期五上午10点15分执行作业。
  
  你不可能用SimpleTrigger来做这些事情。你可以用两者之中的任何一个,但哪个跟合适则取决于你的调度需要。
  
  调度一个作业
  
  让我们通过看一个例子来进入实际讨论。现假定你管理一个部门,无论何时候客户在它的FTP服务器上存储一个文件,都得用电子邮件通知它。我们的作业将用 FTP登陆到远程服务器并下载所有找到的文件。然后,它将发送一封含有找到和下载的文件数量的电子邮件。这个作业很容易就帮助人们整天从手工执行这个任务中解脱出来,甚至连晚上都无须考虑。我们可以设置作业循环不断地每60秒检查一次,而且工作在7×24模式下。这就是Quartz框架完全的用途。
  
  首先创建一个Job类,将执行FTP和Email逻辑。下例展示了Quartz的Job类,它实现了org.quartz.Job接口。
  
  例2.从FTP站点下载文件和发送email的Quartz作业
  
  public class ScanFTPSiteJob implements Job {
  private static Log logger = LogFactory.getLog(ScanFTPSiteJob.class);
  /*
  * Called the scheduler framework at the right time
  */  public void execute(JobExecutionContext context)
  throws JobExecutionException {
  JobDataMap jobDataMap = context.getJobDataMap();
  try {
  // Check the ftp site for files
  File[] files = JobUtil.checkForFiles(jobDataMap);
  JobUtil.sendEmail(jobDataMap, files);
  } catch (Exception ex) {
  throw new JobExecutionException(ex.getMessage());
  }
  }}
  
  我们故意让ScanFTPSiteJob保持很简单。我们为这个例子创建了一个叫做JobUtil的实用类。它不是Quartz的组成部分,但对构建各种作业能重用的实用程序库来说是有意义的。我们可以轻易将那种代码组织进作业类中,quarts 调度器一样好用,因为我们一直在使用quarts,所以那些代码可继续重用。
  
  JobUtil.checkForFiles() and JobUtil.sendEmail()方法使用的参数是Quartz创建的JobDataMap的实例。实例为每个作业的执行而创建,它是向作业类传递配置参数的方法。
  
  这里并没有展示JobUtil的实现,但我们能用Jakarta上的Commons Net轻易地实现FTP和Email功能。
  
  用调度器调用作业
  
  首先创建一个作业,但为使作业能被调度器调用,你得向调度程序说明你的作业的调用时间和频率。这个事情由与作业相关的触发器来完成。因为我们仅仅对大约每60秒循环调用作业感兴趣,所以打算使用SimpleTrigger。
  
  作业和触发器通过Quartz调度器接口而被调度。我们需要从调度器工厂类取得一个调度器的实例。最容易的办法是调用StdSchedulerFactory这个类上的静态方法getDefaultScheduler()。
  
  使用Quartz框架,你需要调用start()方法来启动调度器。例3的代码遵循了大多数Quartz应用的一般模式:创建一个或多个作业,创建和设置触发器,用调度器调度作业和触发器,启动调度器。
  
  例3.Quartz作业通过Quartz调度器而被调度
  
  public class MyQuartzServer {
  public static void main(String[] args) {
  MyQuartzServer server = new MyQuartzServer();
  try {
  server.startScheduler();
  } catch (SchedulerException ex) {
  ex.printStackTrace();
  }
  }
  protected void startScheduler() throws SchedulerException {
  // Use the factory to create a Scheduler instance
  Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
  // JobDetail holds the definition for Jobs
  JobDetail jobDetail = new JobDetail("ScanFTPJob", Scheduler.DEFAULT_GROUP,
  ScanFTPSiteJob.class);// Store job parameters to be used within execute()jobDetail.getJobDataMap().put("FTP_HOST", "\\home\\cavaness\\inbound");
  // Other neccessary Job parameters here
  // Create a Trigger that fires every 60 seconds
  Trigger trigger = TriggerUtils.makeSecondlyTrigger(60);
  // Setup the Job and Trigger with the Scheduler
  scheduler.scheduleJob(jobDetail, trigger );
  // Start the Scheduler running
  scheduler.start();
  }}
  
  编程调度同声明性调度
  
  例3中,我们通过编程的方法调度我们的ScanFTPSiteJob作业。就是说,我们用java代码来设置作业和触发器。Quartz框架也支持在xml文件里面申明性的设置作业调度。申明性方法允许我们更快速地修改哪个作业什么时候被执行。
  
  Quartz框架有一个插件,这个插件负责读取xml配置文件。xml配置文件包含了关于启动Quartz应用的作业和触发器信息。所有xml文件中的作业连同相关的触发器都被加进调度器。你仍然需要编写作业类,但配置那些作业类的调度器则非常动态化。例4展示了一个用申明性方式执行与例3代码相同的逻辑的xml配置文件。
  
  例4.能使用xml文件调度的作业
  
  <?xml version='1.0' encoding='utf-8'?><quartz>
  <job>
  <job-detail>
  <name>ScanFTPSiteJob</name>
  <group>DEFAULT</group>
  <description>
  A job that scans an ftp site for files
  </description>
  <job-class>ScanFTPSiteJob</job-class>
  <job-data-map allows-transient-data="true">
  <entry>
  <key>FTP_HOST</key>
  <value>\home\cavaness\inbound</value>
  </entry>
  <!-- Other neccessary Job parameters here -->
  </job-data-map>
  </job-detail>
  <trigger>
  <simple>
  <name>ScanFTPSiteJobTrigger</name>
  <group>DEFAULT</group>
  <job-name>ScanFTPSiteJob</job-name>
  <job-group>DEFAULT</job-group>
  <start-time>2005-09-11 6:10:00 PM</start-time>
  <!-- repeat indefinitely every 60 seconds -->
  <repeat-count>-1</repeat-count>
  <repeat-interval>60000</repeat-interval>
  </simple>
  </trigger>
  </job></quartz>
  
  你可以将xml文件中的元素跟例3代码作个比较,它们从概念上来看是相同的。使用例4式的申明性方法的好处是维护变得极其简单,只需改变xml配置文件和重新启动Quartz应用即可。无须修改代码,无须重新编译,无须重新部署。
  
  有状态和无状态作业
  
  在本文中你所看到的作业到是无状态的。这意味着在两次作业执行之间,不会去维护作业执行时JobDataMap的状态改变。如果你需要能增、删,改JobDataMap的值,而且能让作业在下次执行时能看到这个状态改变,则需要用Quartz有状态作业。
  
  如果你是一个有经验的EJB开发者的话,深信你会立即退缩,因为有状态带有负面含义。这主要是由于EJB带来的伸缩性问题。Quartz有状态作业实现了org.quartz.StatefulJob接口。无状态和有状态作业的关键不同是有状态作业在每次执行时只有一个实例。大多数情况下,有状态的作业不回带来大的问题。然而,如果你有一个需要频繁执行的作业或者需要很长时间才能完成的作业,那么有状态作业可能给你带来伸缩性问题。
  
  Quartz框架的其他特征
  
  Quartz框架有一个丰富的特征集。事实上,quarts有太多特性以致不能在一种情况中全部领会,下面列出了一些有意思的特征,但没时间在此详细讨论。
  
  监听器和插件
  
  每个人都喜欢监听和插件。今天,几乎下载任何开源框架,你必定会发现支持这两个概念。监听是你创建的java类,当关键事件发生时会收到框架的回调。例如,当一个作业被调度、没有调度或触发器终止和不再打火时,这些都可以通过设置来来通知你的监听器。Quartz框架包含了调度器监听、作业和触发器监听。你可以配置作业和触发器监听为全局监听或者是特定于作业和触发器的监听。
  
  一旦你的一个具体监听被调用,你就能使用这个技术来做一些你想要在监听类里面做的事情。例如,你如果想要在每次作业完成时发送一个电子邮件,你可以将这个逻辑写进作业里面,也可以JobListener里面。写进JobListener的方式强制使用松耦合有利于设计上做到更好。
  
  Quartz插件是一个新的功能特性,无须修改 Quartz源码便可被创建和添加进Quartz框架。他为想要扩展Quartz框架又没有时间提交改变给Quartz开发团队和等待新版本的开发人员而设计。如果你熟悉Struts插件的话,那么完全可以理解Quartz插件的使用。
  
  与其Quartz提供一个不能满足你需要的有限扩展点,还不如通过使用插件来拥有可修整的扩展点。
  
  集群Quartz应用
  
  Quartz应用能被集群,是水平集群还是垂直集群取决于你自己的需要。集群提供以下好处:
  
  ·伸缩性
  ·搞可用性
  ·负载均衡
  
  目前,Quartz只能借助关系数据库和JDBC作业存储支持集群。将来的版本这个制约将消失并且用RAMJobStore集群将是可能的而且将不需要数据库的支持。
  
  Quartz web应用
  
  使用框架几个星期或几个月后,Quartz用户所显示的需求之一是需要集成Quartz到图形用户界面中。目前Quartz框架已经有一些工具允许你使用Java servlet来初始化和启动Quartz。一旦你可以访问调度器实例,你就可以把它存储在web容器的servlet上下文中(ServletContext中)并且可以通过调度器接口管理调度环境。
  
  幸运的是一些开发者已正影响着单机Quartz web应用,它用来更好地管理调度器环境。构建在若干个流行开源框架如Struts和Spring之上的图形用户界面支持很多功能,这些功能都被包装进一个简单接口。GUI的一个画面如图1所示:
   
  图1.Quartz Web应用允许比较容易地管理Quartz环境。
  
  Quartz的下一步计划
  
  Quartz是一个活动中的工程。Quartz开发团队明确表示不会停留在已有的荣誉上。Quartz下一个主要版本已经在启动中。你可以在OpenSymphony的 wiki上体验一下Quartz 2.0的设计和特征。
  
  总之,Quartz用户每天都自由地添加特性建议和设计创意以便能被核心框架考虑(看重)。
  
分享到:
评论

相关推荐

    quartz的demo实例

    这个"quartz-core-learning-master"压缩包文件很可能是Quartz的示例项目,旨在帮助初学者理解并快速上手Quartz的基本用法。让我们深入探讨一下Quartz的核心概念和功能。 1. **核心概念** - **Job**: Job是Quartz中...

    quartz-2.2.2-distribution.rar

    通过研究这些示例,开发者可以快速上手 Quartz 并在自己的项目中实现复杂的定时任务。 综上所述,这个压缩包提供的 Quartz 2.2.2 版本和 Spring Boot 2.x 的集成示例,对于学习和使用 Quartz 进行任务调度具有很高...

    quartz框架demo,适用于初学者

    这个"quartz框架demo"是专为初学者设计的,旨在帮助他们快速理解和上手Quartz的使用。 首先,我们要了解Quartz的核心概念: 1. **Job**:在Quartz中,Job代表一个具体的任务,你需要实现`org.quartz.Job`接口,并...

    quartz1.6.6 实例

    然而,这个实例主要关注的是基础用法,帮助初学者快速理解和上手Quartz。 总的来说,"quartz1.6.6 实例"是一个很好的学习资源,它通过一个简单的项目展示了如何在Java程序中集成Quartz进行任务调度。通过阅读和运行...

    quartz定时任务Demo,直接可运行

    这个Demo提供了一个很好的起点,帮助开发者快速上手Quartz,理解其核心概念和用法。通过实践,你可以更深入地了解如何根据业务需求定制和优化定时任务,提高系统自动化水平。同时,也可以借此机会学习Java多线程和...

    Quartz中文入门教程

    上手Quartz Quartz内部架构 作业 作业管理和存储 有效作业存储 作业和触发器 调度一个作业 用调度器(Scheduler)调用你的作业 编程调度同声明性调度 有状态和无状态作业 Quartz框架的其他特征 Quartz下...

    quartz作业调度demo

    本示例"quartz作业调度demo"是针对初学者设计的,旨在帮助他们快速理解并上手Quartz的基本用法。 Quartz的核心概念包括Job(作业)、Trigger(触发器)和Scheduler(调度器)。以下是对这些关键概念的详细解释: 1...

    QuartzDemo.rar

    总的来说,"QuartzDemo.rar"提供了一个很好的起点,让你能够快速上手Quartz.NET并将其应用于实际的C#项目中。通过研究这个实例,你将能掌握如何在.NET环境中实现定时任务,为你的项目增添更多自动化功能。

    quartz-2.2.0

    同时,阅读提供的文档示例可以帮助理解和快速上手Quartz 2.2.0的使用,例如如何配置Scheduler工厂,如何编写和调度Job,以及如何处理各种复杂的触发规则。 通过熟练掌握Quartz,开发者可以构建出灵活、可靠且可扩展...

    quartz任务调度

    Quartz是一款开源的作业调度框架,它允许开发者创建、安排和执行定时任务。在Java应用中,Quartz常被用来实现复杂的时间驱动业务逻辑。...这个实例应该可以帮助初学者快速上手Quartz,理解其基本概念和用法。

    Quartz简单Demo

    总结,Quartz简单Demo是一个实用的学习资源,它涵盖了Quartz调度框架的基本功能和实际操作,帮助开发者快速上手Quartz,实现复杂的应用场景中的定时任务管理。通过研究这个Demo,你可以深入理解如何利用Quartz来构建...

    Quartz调度框架

    #### 三、快速上手Quartz 想要开始使用Quartz进行任务调度非常容易。首先,你需要从OpenSymphony官方网站下载Quartz的最新版本。下载后,解压缩文件会得到一个名为`quartz.jar`的核心二进制文件,将其集成到你的...

    quartz开发指南

    为了快速上手Quartz,开发者首先需要理解基本的概念,并创建一个简单的示例项目。步骤如下: 1. **环境搭建**:安装必要的开发工具和库。 2. **创建项目**:使用IDE或命令行工具创建Java项目。 3. **添加依赖**:将...

    quartzTask.zip

    QuartzTask.zip是一个压缩包,...总之,QuartzTask.zip提供的示例可以帮助你快速上手Quartz框架,理解如何创建、调度和执行定时任务。随着对Quartz的深入学习,你将能够构建更复杂的定时任务系统,满足各种项目需求。

    深入浅出量化实验室

    10分钟上手Quartz (UQER版) 这部分提供了UQER版本的快速入门指南,具体步骤与上述类似,但可能会有一些细节上的差异。 - **定义参数**:设定回测的相关参数。 - **构建日间策略**:根据UQER版本的特点编写策略...

    关于quartz的快速上手手册

    【Quartz 快速上手指南】 Quartz 是一个强大的开源作业调度框架,适用于 J2EE 和 J2SE 应用程序。它提供了一种灵活的方式来安排和执行各种任务,无论是简单的单次任务还是复杂的定时任务。Quartz 的设计使得它可以...

    Quartz-1.8.4官方

    7. **示例项目**:官方提供的示例代码可以帮助开发者快速上手,理解如何创建、调度任务,以及如何处理任务执行的各种情况。 在使用quartz-1.8.4这个版本时,开发者可以: - **导入jar包**:将下载的jar包添加到...

    quartz-2.4.0-SNAPSHOT-distribution.tar.gz

    4. **示例**:`examples`目录下的代码示例可以帮助初学者快速上手,例如"simple"示例,展示了如何创建、调度和执行一个简单的任务。这些示例通常涵盖基本的使用场景,如立即执行、定时执行、依赖于其他任务的执行等...

Global site tag (gtag.js) - Google Analytics