我有以下课程
public class MyEmailService
{
public async Task<bool> SendAdminEmails()
{
...
}
public async Task<bool> SendUserEmails()
{
...
}
}
public interface IMyEmailService
{
Task<bool> SendAdminEmails();
Task<bool> SendUserEmails();
}
我安装了最新的Quartz 2.4.1 Nuget package,因为我想在我的网络应用中使用轻量级的调度程序而没有单独的SQL Server数据库。
我需要安排方法
SendUserEmails
每周星期一17:00,星期二17:00&amp;星期三17:00 SendAdminEmails
每周星期四09:00,星期五9:00 在ASP.NET Core中使用Quartz安排这些方法需要哪些代码?我还需要知道如何在ASP.NET Core中启动Quartz,因为互联网上的所有代码示例仍然引用了以前版本的ASP.NET。
对于以前版本的ASP.NET,我可以find a code sample,但我不知道如何在ASP.NET Core中启动Quartz以开始测试。
我在哪里将JobScheduler.Start();
放在ASP.NET Core中?
答案 0 :(得分:54)
假设工具:Visual Studio 2017 RTM,.NET Core 1.1,.NET Core SDK 1.0,SQL Server Express 2016 LocalDB。
在网络应用程序.csproj中:
<Project Sdk="Microsoft.NET.Sdk.Web">
<!-- .... existing contents .... -->
<!-- add the following ItemGroup element, it adds required packages -->
<ItemGroup>
<PackageReference Include="Quartz" Version="3.0.0-alpha2" />
<PackageReference Include="Quartz.Serialization.Json" Version="3.0.0-alpha2" />
</ItemGroup>
</Project>
在Program
类中(默认情况下由Visual Studio搭建):
public class Program
{
private static IScheduler _scheduler; // add this field
public static void Main(string[] args)
{
var host = new WebHostBuilder()
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseStartup<Startup>()
.UseApplicationInsights()
.Build();
StartScheduler(); // add this line
host.Run();
}
// add this method
private static void StartScheduler()
{
var properties = new NameValueCollection {
// json serialization is the one supported under .NET Core (binary isn't)
["quartz.serializer.type"] = "json",
// the following setup of job store is just for example and it didn't change from v2
// according to your usage scenario though, you definitely need
// the ADO.NET job store and not the RAMJobStore.
["quartz.jobStore.type"] = "Quartz.Impl.AdoJobStore.JobStoreTX, Quartz",
["quartz.jobStore.useProperties"] = "false",
["quartz.jobStore.dataSource"] = "default",
["quartz.jobStore.tablePrefix"] = "QRTZ_",
["quartz.jobStore.driverDelegateType"] = "Quartz.Impl.AdoJobStore.SqlServerDelegate, Quartz",
["quartz.dataSource.default.provider"] = "SqlServer-41", // SqlServer-41 is the new provider for .NET Core
["quartz.dataSource.default.connectionString"] = @"Server=(localdb)\MSSQLLocalDB;Database=Quartz;Integrated Security=true"
};
var schedulerFactory = new StdSchedulerFactory(properties);
_scheduler = schedulerFactory.GetScheduler().Result;
_scheduler.Start().Wait();
var userEmailsJob = JobBuilder.Create<SendUserEmailsJob>()
.WithIdentity("SendUserEmails")
.Build();
var userEmailsTrigger = TriggerBuilder.Create()
.WithIdentity("UserEmailsCron")
.StartNow()
.WithCronSchedule("0 0 17 ? * MON,TUE,WED")
.Build();
_scheduler.ScheduleJob(userEmailsJob, userEmailsTrigger).Wait();
var adminEmailsJob = JobBuilder.Create<SendAdminEmailsJob>()
.WithIdentity("SendAdminEmails")
.Build();
var adminEmailsTrigger = TriggerBuilder.Create()
.WithIdentity("AdminEmailsCron")
.StartNow()
.WithCronSchedule("0 0 9 ? * THU,FRI")
.Build();
_scheduler.ScheduleJob(adminEmailsJob, adminEmailsTrigger).Wait();
}
}
工作类的一个例子:
public class SendUserEmailsJob : IJob
{
public Task Execute(IJobExecutionContext context)
{
// an instance of email service can be obtained in different ways,
// e.g. service locator, constructor injection (requires custom job factory)
IMyEmailService emailService = new MyEmailService();
// delegate the actual work to email service
return emailService.SendUserEmails();
}
}
首先,根据this announcement,你必须使用Quartz的v3,因为它的目标是.NET Core。
目前,只有v3软件包的alpha版本为available on NuGet。看起来团队花了很多精力发布2.5.0,而不是针对.NET Core。然而,在他们的GitHub回购中,master
分支已经专门用于v3,基本上,open issues for v3 release似乎并不重要,大多数都是旧的愿望清单项目,恕我直言。由于最近的commit activity非常低,我预计v3会在几个月内发布,或者可能是半年 - 但没有人知道。
如果Web应用程序将在IIS下托管,则必须考虑工作进程的回收/卸载行为。 ASP.NET Core Web应用程序作为常规.NET Core进程运行,与w3wp.exe分开 - IIS仅用作反向代理。然而,当循环或卸载w3wp.exe的实例时,相关的.NET Core应用程序进程也会发出信号以退出(根据this)。
Web应用程序也可以在非IIS反向代理(例如NGINX)后面自行托管,但我会假设您使用IIS,并相应地缩小我的答案。
post referenced by @darin-dimitrov:
中详细解释了回收/卸载引入的问题尽管存在上述问题,我仍然可以想到将这些电子邮件作业托管在Web应用程序中的一个理由。决定只有一种应用程序模型(ASP.NET)。这种方法简化了学习曲线,部署程序,生产监控等。
如果您不想引入后端微服务(这将是移动电子邮件作业的好地方),那么有必要克服IIS回收/卸载行为,并在Web应用程序中运行Quartz。
或许你有其他原因。
在您的方案中,作业执行的状态必须保持在进程外。因此,默认 RAMJobStore 不适合,您必须使用 ADO.NET Job Store 。
由于您在问题中提到了SQL Server,我将提供SQL Server数据库的示例设置。
我假设您使用Visual Studio 2017和最新/最新版本的.NET Core工具。我的是.NET Core Runtime 1.1和.NET Core SDK 1.0。
对于数据库设置示例,我将在SQL Server 2016 Express LocalDB中使用名为Quartz
的数据库。数据库设置脚本可以是found here。
首先,向Web应用程序.csproj添加必需的包引用(或者在Visual Studio中使用NuGet包管理器GUI执行此操作):
<Project Sdk="Microsoft.NET.Sdk.Web">
<!-- .... existing contents .... -->
<!-- the following ItemGroup adds required packages -->
<ItemGroup>
<PackageReference Include="Quartz" Version="3.0.0-alpha2" />
<PackageReference Include="Quartz.Serialization.Json" Version="3.0.0-alpha2" />
</ItemGroup>
</Project>
在Migration Guide和V3 Tutorial的帮助下,我们可以弄清楚如何启动和停止调度程序。我更喜欢将其封装在一个单独的类中,我们将其命名为QuartzStartup
。
using System;
using System.Collections.Specialized;
using System.Threading.Tasks;
using Quartz;
using Quartz.Impl;
namespace WebApplication1
{
// Responsible for starting and gracefully stopping the scheduler.
public class QuartzStartup
{
private IScheduler _scheduler; // after Start, and until shutdown completes, references the scheduler object
// starts the scheduler, defines the jobs and the triggers
public void Start()
{
if (_scheduler != null)
{
throw new InvalidOperationException("Already started.");
}
var properties = new NameValueCollection {
// json serialization is the one supported under .NET Core (binary isn't)
["quartz.serializer.type"] = "json",
// the following setup of job store is just for example and it didn't change from v2
["quartz.jobStore.type"] = "Quartz.Impl.AdoJobStore.JobStoreTX, Quartz",
["quartz.jobStore.useProperties"] = "false",
["quartz.jobStore.dataSource"] = "default",
["quartz.jobStore.tablePrefix"] = "QRTZ_",
["quartz.jobStore.driverDelegateType"] = "Quartz.Impl.AdoJobStore.SqlServerDelegate, Quartz",
["quartz.dataSource.default.provider"] = "SqlServer-41", // SqlServer-41 is the new provider for .NET Core
["quartz.dataSource.default.connectionString"] = @"Server=(localdb)\MSSQLLocalDB;Database=Quartz;Integrated Security=true"
};
var schedulerFactory = new StdSchedulerFactory(properties);
_scheduler = schedulerFactory.GetScheduler().Result;
_scheduler.Start().Wait();
var userEmailsJob = JobBuilder.Create<SendUserEmailsJob>()
.WithIdentity("SendUserEmails")
.Build();
var userEmailsTrigger = TriggerBuilder.Create()
.WithIdentity("UserEmailsCron")
.StartNow()
.WithCronSchedule("0 0 17 ? * MON,TUE,WED")
.Build();
_scheduler.ScheduleJob(userEmailsJob, userEmailsTrigger).Wait();
var adminEmailsJob = JobBuilder.Create<SendAdminEmailsJob>()
.WithIdentity("SendAdminEmails")
.Build();
var adminEmailsTrigger = TriggerBuilder.Create()
.WithIdentity("AdminEmailsCron")
.StartNow()
.WithCronSchedule("0 0 9 ? * THU,FRI")
.Build();
_scheduler.ScheduleJob(adminEmailsJob, adminEmailsTrigger).Wait();
}
// initiates shutdown of the scheduler, and waits until jobs exit gracefully (within allotted timeout)
public void Stop()
{
if (_scheduler == null)
{
return;
}
// give running jobs 30 sec (for example) to stop gracefully
if (_scheduler.Shutdown(waitForJobsToComplete: true).Wait(30000))
{
_scheduler = null;
}
else
{
// jobs didn't exit in timely fashion - log a warning...
}
}
}
}
注意1.在上面的示例中,SendUserEmailsJob
和SendAdminEmailsJob
是实现IJob
的类。 IJob
界面与IMyEmailService
略有不同,因为它返回void Task
而不是Task<bool>
。两个作业类都应该IMyEmailService
作为依赖项(可能是构造函数注入)。
注意2.对于能够及时退出的长期工作,在IJob.Execute
方法中,应该观察IJobExecutionContext.CancellationToken
的状态。这可能需要更改IMyEmailService
接口,以使其方法接收CancellationToken
参数:
public interface IMyEmailService
{
Task<bool> SendAdminEmails(CancellationToken cancellation);
Task<bool> SendUserEmails(CancellationToken cancellation);
}
在ASP.NET Core中,应用程序引导代码驻留在类Program
中,与控制台应用程序非常相似。调用Main
方法来创建Web主机,运行它,并等到它退出:
public class Program
{
public static void Main(string[] args)
{
var host = new WebHostBuilder()
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseStartup<Startup>()
.UseApplicationInsights()
.Build();
host.Run();
}
}
最简单的事情就是在QuartzStartup.Start
方法中调用Main
,就像我在TL中做的那样; DR。但由于我们必须正确处理进程关闭,我更喜欢以更一致的方式挂接启动和关闭代码。
这一行:
.UseStartup<Startup>()
指的是一个名为Startup
的类,它在Visual Studio中创建新的 ASP.NET Core Web Application 项目时被搭建。 Startup
类看起来像这样:
public class Startup
{
public Startup(IHostingEnvironment env)
{
// scaffolded code...
}
public IConfigurationRoot Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// scaffolded code...
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
// scaffolded code...
}
}
很明显,应该在QuartzStartup.Start
类的一个方法中插入对Startup
的调用。问题是,QuartzStartup.Stop
应该挂钩。
在旧版.NET Framework中,ASP.NET提供了IRegisteredObject
接口。根据{{3}}和this post,在ASP.NET Core中,它已替换为IApplicationLifetime
。答对了。 IApplicationLifetime
的实例可以通过参数注入Startup.Configure
方法。
为了保持一致性,我会将QuartzStartup.Start
和QuartzStartup.Stop
挂钩到IApplicationLifetime
:
public class Startup
{
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(
IApplicationBuilder app,
IHostingEnvironment env,
ILoggerFactory loggerFactory,
IApplicationLifetime lifetime) // added this parameter
{
// the following 3 lines hook QuartzStartup into web host lifecycle
var quartz = new QuartzStartup();
lifetime.ApplicationStarted.Register(quartz.Start);
lifetime.ApplicationStopping.Register(quartz.Stop);
// .... original scaffolded code here ....
}
// ....the rest of the scaffolded members ....
}
请注意,我已使用额外的Configure
参数扩展了IApplicationLifetime
方法的签名。根据{{3}},ApplicationStopping
将阻止,直到注册的回调完成。
我能够在IIS上观察IApplicationLifetime.ApplicationStopping
挂钩的预期行为,并安装了最新的ASP.NET Core模块。 IIS Express(与Visual Studio 2017社区RTM一起安装)和具有过时版本的ASP.NET Core模块的IIS都没有一致地调用IApplicationLifetime.ApplicationStopping
。我认为这是因为documentation被修复了。
您可以安装最新版本的ASP.NET核心模块documentation。按照&#34;安装最新的ASP.NET核心模块&#34; 部分中的说明进行操作。
我还看了一下FluentScheduler,因为它被@Brice Molesti提议作为替代库。令我的第一印象是,与Quartz相比,FluentScheduler是一个相当简单且不成熟的解决方案。例如,FluentScheduler没有提供作业状态持久性和集群执行等基本功能。
答案 1 :(得分:5)
除了@ felix-b答案。将DI添加到作业中。也可以使QuartzStartup Start异步。
基于以下答案:https://stackoverflow.com/a/42158004/1235390
public class QuartzStartup
{
public QuartzStartup(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public async Task Start()
{
// other code is same
_scheduler = await schedulerFactory.GetScheduler();
_scheduler.JobFactory = new JobFactory(_serviceProvider);
await _scheduler.Start();
var sampleJob = JobBuilder.Create<SampleJob>().Build();
var sampleTrigger = TriggerBuilder.Create().StartNow().WithCronSchedule("0 0/1 * * * ?").Build();
await _scheduler.ScheduleJob(sampleJob, sampleTrigger);
}
}
JobFactory类
public class JobFactory : IJobFactory
{
private IServiceProvider _serviceProvider;
public JobFactory(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
{
return _serviceProvider.GetService(bundle.JobDetail.JobType) as IJob;
}
public void ReturnJob(IJob job)
{
(job as IDisposable)?.Dispose();
}
}
启动类:
public void ConfigureServices(IServiceCollection services)
{
// other code is removed for brevity
// need to register all JOBS by their class name
services.AddTransient<SampleJob>();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, IApplicationLifetime applicationLifetime)
{
var quartz = new QuartzStartup(_services.BuildServiceProvider());
applicationLifetime.ApplicationStarted.Register(() => quartz.Start());
applicationLifetime.ApplicationStopping.Register(quartz.Stop);
// other code removed for brevity
}
具有构造函数依赖项注入的SampleJob类:
public class SampleJob : IJob
{
private readonly ILogger<SampleJob> _logger;
public SampleJob(ILogger<SampleJob> logger)
{
_logger = logger;
}
public async Task Execute(IJobExecutionContext context)
{
_logger.LogDebug("Execute called");
}
}
答案 2 :(得分:2)
我不知道如何使用Quartz,但我已经尝试了与其他图书馆相同的情景。我在这里如何点击它
安装FluentScheduler
Install-Package FluentScheduler
像这样使用
var registry = new Registry();
JobManager.Initialize(registry);
JobManager.AddJob(() => MyEmailService.SendAdminEmails(), s => s
.ToRunEvery(1)
.Weeks()
.On(DayOfWeek.Monday)
.At(17, 00));
JobManager.AddJob(() => MyEmailService.SendAdminEmails(), s => s
.ToRunEvery(1)
.Weeks()
.On(DayOfWeek.Wednesday)
.At(17, 00));
JobManager.AddJob(() => MyEmailService.SendUserEmails(), s => s
.ToRunEvery(1)
.Weeks()
.On(DayOfWeek.Thursday)
.At(09, 00));
JobManager.AddJob(() => MyEmailService.SendUserEmails(), s => s
.ToRunEvery(1)
.Weeks()
.On(DayOfWeek.Friday)
.At(09, 00));
可在此处找到文档FluentScheduler on GitHub
答案 3 :(得分:0)
可接受的答案很好地涵盖了该主题,但是最新的Quartz版本改变了某些事情。以下基于this article展示了Quartz 3.0.x和ASP.NET Core 2.2的快速入门:
50
import apache_beam as beam
# Pipeline options:
options = beam.options.pipeline_options.PipelineOptions()
gcloud_options = options.view_as(beam.options.pipeline_options.GoogleCloudOptions)
gcloud_options.job_name = 'test'
gcloud_options.project = 'project'
gcloud_options.staging_location = 'gs://staging'
gcloud_options.temp_location = 'gs://tmp'
gcloud_options.region = 'europe-west2'
# Worker options:
worker_options = options.view_as(beam.options.pipeline_options.WorkerOptions)
worker_options.disk_size_gb = 30
worker_options.max_num_workers = 10
# Standard options:
options.view_as(beam.options.pipeline_options.StandardOptions).runner = 'DataflowRunner'
# Pipeline:
PL = beam.Pipeline(options=options)
(
PL | 'read' >> beam.io.ReadFromText('gs://input.txt')
| 'write' >> beam.io.WriteToText ('gs://output.txt', num_shards=1)
)
PL.run()
public class QuartzJobFactory : IJobFactory
{
private readonly IServiceProvider _serviceProvider;
public QuartzJobFactory(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
{
var jobDetail = bundle.JobDetail;
var job = (IJob)_serviceProvider.GetService(jobDetail.JobType);
return job;
}
public void ReturnJob(IJob job) { }
}
答案 4 :(得分:0)
在ASP.NET Core中使用Quartz计划这些方法需要什么代码?我还需要知道如何在ASP.NET Core中启动Quartz,因为Internet上的所有代码示例仍然引用ASP.NET的早期版本。
嗨,现在有一个很好的石英DI可以初始化和使用
[DisallowConcurrentExecution]
public class Job1 : IJob
{
private readonly ILogger<Job1> _logger;
public Job1(ILogger<Job1> logger)
{
_logger = logger;
}
public async Task Execute(IJobExecutionContext context)
{
_logger.LogInformation("Start job1");
await Task.Delay(2, context.CancellationToken);
_logger?.LogInformation("End job1");
}
}
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddQuartz(cfg =>
{
cfg.UseMicrosoftDependencyInjectionJobFactory(opt =>
{
opt.AllowDefaultConstructor = false;
});
cfg.AddJob<Job1>(jobCfg =>
{
jobCfg.WithIdentity("job1");
});
cfg.AddTrigger(trigger =>
{
trigger
.ForJob("job1")
.WithIdentity("trigger1")
.WithSimpleSchedule(x => x
.WithIntervalInSeconds(10)
.RepeatForever());
});
});
services.AddQuartzHostedService(opt =>
{
opt.WaitForJobsToComplete = true;
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// standart impl
}
}