In an MVC5 web application using Net 4.8, I am trying to do the following:
When launching the application, I would like a scheduler to trigger a job every 2 Minutes.
The job reads and processes messages from a message queue.
I previously used Hangfire for this and it worked quite well.
However, I was told not to use Hangfire for this application. As an alternative, I opted for Quartz.NET here but I am currently having trouble setting up and triggering
the desired action. Following the Quartz.NET documentation, this is my current setup.
public class DummyJob :IJob
{
private readonly ISomeInterface _someInterface;
public DummyJob(ISomeInterface someInterface)
{
_someInterface = someInterface;
}
public async Task Execute(IJobExecutionContext context)
{
await _someInterface.ProcessMessages();
}
}
Next, the job configuration
public class JobScheduler
{
public static void Start()
{
// Run every 2 minutes
const string cron = #"0 0/2 * * * ?";
ISchedulerFactory schedulerFactory = new StdSchedulerFactory();
var scheduler = (IScheduler)schedulerFactory.GetScheduler().Result;
scheduler.Start();
var job = JobBuilder.Create<DummyJob>().Build();
var trigger = TriggerBuilder
.Create()
.WithCronSchedule(cron)
.Build();
scheduler.ScheduleJob(job, trigger);
}
}
I tried starting the job from both Startup.cs and Global.asax. Although the job was created, it did not fire when launching the application.
public void Configuration(IAppBuilder app)
{
[...]
JobScheduler.JobScheduler.Start();
}
What am I missing in terms of setting up QuartzNet? Do I need to put JobScheduler.JobScheduler.Start(); into Global.asax.cs or Startup.cs?
Related
in my rested-web-service project, i need to check database in one hour intervals, this method should continue to work all the time, even if there is not user, and this method is single per hosted server.(some thing like cron in linux but i will host is iis)
i have created a hostedService using microsft docs,
with merely modifying the execute intervals (from 5 sec to 1 hours),
**but problem occure after i deploy webservice solution in iis,this hosted servicerun if swagger page is open when i close browser or not calling rest-service, there is no logs in database
another problem is if a open if i open swagger ui and call webservices from mobile-client there is multiple logs instead of only one per hour.**
i bellive there is two problem here, first TimedHostedService scope is not singleton and it is created per request(scoped) and second problem is application is not kept alive where there is not any session in swagger-ui (or there is no user for werb services).
i hava tried creating Async Method for this purpose that method call-it self with task.delay(onHour)
but this didnt work either.
public class TimedHostedService : IHostedService, IDisposable
{
private int executionCount = 0;
private readonly ILogger<TimedHostedService> _logger;
private Timer? _timer = null;
public TimedHostedService(ILogger<TimedHostedService> logger)
{
_logger = logger;
}
public Task StartAsync(CancellationToken stoppingToken)
{
_timer = new Timer(DoWork, null, TimeSpan.Zero,
TimeSpan.FromHours(1));
return Task.CompletedTask;
}
private void DoWork(object? state)
{
var count = Interlocked.Increment(ref executionCount);
_logger.LogInformation(
"Timed Hosted Service is working. Count: {Count}", count);
}
public Task StopAsync(CancellationToken stoppingToken)
{
_timer?.Change(Timeout.Infinite, 0);
return Task.CompletedTask;
}
public void Dispose()
{
_timer?.Dispose();
}
}
We are trying to use quartz for some of our scheduled tasks such as email and record duplication. It seems to be a perfect fit however, the documentation hasn't really been updated for DI stuff and repo unit of work patterns so we have an issue. We have a separate project in our solution where we want to house our scheduler and different jobs, which will have access to our service layer where each Job calls our service. The issue is, we aren't sure how to get access to the scheduler after it gets instantiated in the job runner project's main file.
Main file:
private static async Task Main(string[] args)
{
LogProvider.SetCurrentLogProvider(new ConsoleLogProvider());
// Grab the Scheduler instance from the Factory
StdSchedulerFactory factory = new StdSchedulerFactory();
IScheduler scheduler = await factory.GetScheduler();
// and start it off
await scheduler.Start();
// and last shut down the scheduler when you are ready to close your program
await scheduler.Shutdown();
}
Typical Job:
using Quartz;
using Compyl.AppLogic.AssessmentService;
namespace JobRunner.Jobs
{
public class AssessmentDuplicationJob : IJob
{
private readonly IAssessmentService _assessmentService;
public AssessmentDuplicationJob(IAssessmentService assessmentService)
{
_assessmentService = assessmentService;
}
public async Task Execute(IJobExecutionContext context)
{
var dataMap = context.MergedJobDataMap;
var id = dataMap.GetInt("id");
var shouldPopulateAnswers = dataMap.GetBoolean("shouldPopulateAnswers");
_assessmentService.DuplicateAssessment(id, shouldPopulateAnswers);
}
}
}
we want to have a class or method in the job runner project that houses the scheduler through DI and a method(s) like
[Inject] IScheduler scheduler
public ScheduleEntity (T entity)
{
var job = createJob(entity)//another method that creates the job and jobdatamap this is easy enough
var trigger = createTrigger(entity)// same as above
scheduler.ScheduleJob(job, trigger)
}
Then we would call the ScheduleEntity method wherever we needed it in our webapp.
Not sure if this is the "correct" way, but what we're doing is setting up the default scheduler in the DI container and then using it in each component we need it by calling the factory's get scheduler method, and then using a static class from our other project to create and schedule jobs, using the scheduler as a parameter.
All the code samples I've seen so far for Azure WebJobs rely on some kind of trigger (e.g. TimerTrigger or QueueTrigger).
I am looking specifically at WebJobs SDK 3.x, by the way.
So. For a triggerless WebJob (Windows Service-alike one), am I expected to use NoAutomaticTrigger and find a way to kickoff my "main" code manually?
Or should I resort to implementing and registering a class that implements the IHostedService interface?
So far that's the approach I'm taking but it feels more of a hack than a recommended way.
I have not even tried to deploy this code and only ran it on my local machine, so I am afraid that the publishing process will confirm my code is not suitable for Azure WebJobs in its current form.
EntryPoint.cs
This is how the application is being bootstrap when the process is starting.
using Microsoft.Azure.ServiceBus;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace AbcCorp.Jobs
{
public static class Program
{
static async Task Main(string[] args)
{
var config = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT")}.json", false)
.Build();
var hostBuilder = new HostBuilder()
.ConfigureWebJobs(builder => { builder.AddAzureStorageCoreServices(); })
.ConfigureServices(serviceCollection =>
{
ConfigureServices(serviceCollection, config);
serviceCollection.AddHostedService<ConsoleApplication>();
});
using (var host = hostBuilder.Build())
await host.RunAsync();
}
private static IServiceCollection ConfigureServices(IServiceCollection services, IConfigurationRoot configuration)
{
services.AddTransient<ConsoleApplication>();
// ... more DI registrations
return services;
}
}
}
ConsoleApplication.cs
This would normally be implemented as a function with a trigger.
The thing is, I want this code to only run once on the process startup.
It will start listening on the service bus events using the regular Microsoft.Azure.ServiceBus SDK package.
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;
using AbcCorp.Internal.Microsoft.Azure.ServiceBus;
using AbcCorp.Api.Messaging;
namespace AbcCorp.Jobs
{
public sealed class ConsoleApplication: IHostedService
{
private readonly IReceiver<SubmissionNotification> _messageReceiver;
private readonly MessageHandler _messageHandler;
public ConsoleApplication(IReceiver<SubmissionNotification> messageReceiver, MessageHandler messageHandler)
{
_messageReceiver = messageReceiver;
_messageHandler = messageHandler;
}
public Task StartAsync(CancellationToken cancellationToken)
{
_messageReceiver.StartListening(_messageHandler.HandleMessage, _messageHandler.HandleException);
return Task.Delay(Timeout.Infinite);
}
public Task StopAsync(CancellationToken cancellationToken)
{
_messageReceiver.Dispose();
return Task.CompletedTask;
}
}
}
So you want a console application to run in a WebJob and listen to messages. You don't really care about WebJob magic like triggers, it's just a place to run your console app. I've done the exact same thing before.
I found the IHostedService abstraction to be very helpful, but I didn't like their SDK. I found it bloated and hard to use. I didn't want to take a large dependency in order use a large array of special magic Azure stuff, when all I wanted to do was run a console application in a WebJob for now, and maybe move it elsewhere later.
So I ended just deleting that dependency, stealing the Shutdown code from the SDK and writing my own Service Host. The result is on my Github Repo azure-webjob-host. Feel free to use it or raid it for ideas. I don't know, maybe if I did it again I'd have another attempt at getting the SDK to work, but I present this is a bit of an alternative to the SDK.
Basically I wrote an IServiceHost not too different from yours (except that StartAsync exited when stuff started instead of just hanging). Then I wrote my own service host, which is basically just a loop:
await _service.StartAsync(cancellationToken);
while (!token.IsCancellationRequested){await Task.Delay(1000);}
await _service.StopAsync(default);
Then I stole the WebJobsShutdownWatcher code from their repo.
Then I created an IServiceHost that started my message handler. (I was using Rabbit, which has nothing to do with triggers or azure stuff)
public class MessagingService : IHostedService, IDisposable
{
public MessagingService(ConnectionSettings connectionSettings,
AppSubscriberSettings subscriberSettings,
MessageHandlerTypeMapping[] messageHandlerTypeMappings,
ILogger<MessagingService> logger)
{
....
}
public async Task StartAsync(CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
await Task.WhenAll(subscribers.Value.Select(s => s.StartSubscriptionAsync()));
}
public async Task StopAsync(CancellationToken cancellationToken)
{
...
}
public void Dispose()
{
...
}
}
Then I put that all together into something like this:
IHostedService myService = new MyService();
using (var host = new ServiceHostBuilder().HostService(myService))
{
await host.RunAsync(default);
}
I have some workers attached to service bus topics and what we do is the following (ServiceBusClient is a custom Class that contains our Subscription Client):
public override Task StartAsync(CancellationToken cancellationToken)
{
_serviceBusClient.RegisterOnMessageHandlerAndReceiveMessages(MessageReceivedAsync);
_logger.LogDebug($"Started successfully the Import Client. Listening for messages...");
return base.StartAsync(cancellationToken);
}
public void RegisterOnMessageHandlerAndReceiveMessages(Func<Message, CancellationToken, Task> ProcessMessagesAsync)
{
// Configure the message handler options in terms of exception handling, number of concurrent messages to deliver, etc.
var messageHandlerOptions = new MessageHandlerOptions(ExceptionReceivedHandler)
{
// Maximum number of concurrent calls to the callback ProcessMessagesAsync(), set to 1 for simplicity.
// Set it according to how many messages the application wants to process in parallel.
MaxConcurrentCalls = 1,
// Indicates whether MessagePump should automatically complete the messages after returning from User Callback.
// False below indicates the Complete will be handled by the User Callback as in `ProcessMessagesAsync` below.
AutoComplete = false
};
// Register the function that processes messages.
SubscriptionClient.RegisterMessageHandler(ProcessMessagesAsync, messageHandlerOptions);
}
And then you can use DI to instantiate your service bus client and inject on the constructor of your Worker class.
Here i have the initialization of the singleton instance of my custom class Service Bus Client
services.AddSingleton<IServiceBusClient, ServiceBusClient>((p) =>
{
var diagnostics = p.GetService<EventHandling>();
var sbc = new ServiceBusClient(
programOptions.Endpoint,
programOptions.TopicName,
programOptions.Subscriber,
programOptions.SubscriberKey);
sbc.Exception += exception => diagnostics.HandleException(exception);
return sbc;
});
Then on this custom class, i initialize my subscription client
public ServiceBusClient(
string endpoint,
string topicName,
string subscriberName,
string subscriberKey, ReceiveMode mode = ReceiveMode.PeekLock)
{
var connBuilder = new ServiceBusConnectionStringBuilder(endpoint, topicName, subscriberName, subscriberKey);
var connectionString = connBuilder.GetNamespaceConnectionString();
ConnectionString = connectionString;
TopicName = topicName;
SubscriptionName = topicName;
SubscriptionClient = new SubscriptionClient(connectionString, topicName, subscriberName, mode);
}
You can check #george chen's answer from this post How to create service bus trigger webjob?
where instead of creating a receiver and registering a message handler, you can use the in built queue trigger and and write your message handler logic inside it.
I am using Quartz.NET 3.0.7 in my application for some scheduled tasks but when i start scheduler it will increasing memory when scheduler run.i also checked with simple schedule task which print some list of string on console the same issue is happening in this also.i can not understand what is actual issue.my observation is IJob object is released after job work done.I have no idea other than that.
I am creating Jobs Dynamically by passing JobType
private IJobDetail GetJobDetailForType<TInput>(string jobKey, string jobGroup) where TInput : IJob
{
IJobDetail jobDetail = JobBuilder.Create<TInput>()
.WithIdentity(jobKey, jobGroup)
.Build();
return jobDetail;
}
And initialize schedule jobs in this function
private void InitializeSchedulerJob(ScheduleJob scheduleJob)
{
IJobDetail jobDetail;
ITrigger trigger = this.GetTrigger(scheduleJob.Code.ToString(), tenantCode, scheduleJob.CornSchedule);
if (scheduleJob.Code == (int)EnumHelper.Scheduler.Job.EmailJob)
{
this.logger.LogDebug($"EmailJob");
jobDetail = this.GetJobDetailForType<EmailJob>(scheduleJob.Code.ToString(), tenantCode);
}
this.scheduler.ScheduleJob(jobDetail, trigger);
}
Email Job Code
public class EmailJob : IJob
{
private IServiceProvider serviceProvider;
public EmailJob(IServiceProvider serviceProvider)
{
this.serviceProvider = serviceProvider;
}
public async Task Execute(IJobExecutionContext context)
{
if (this.serviceProvider != null)
{
JobKey jobKey = context.JobDetail.Key;
IEmailScheduleSendService emailScheduleSendServiceNew = this.serviceProvider.GetRequiredService<IEmailScheduleSendService>();
ILogger<EmailJob> logger = this.serviceProvider.GetRequiredService<ILogger<EmailJob>>();
logger.LogDebug($"FROM EXECUTE METHOD | {jobKey.Name} | {jobKey.Group} | START");
await emailScheduleSendServiceNew.EmailScheduleSendAsync(context);
logger.LogDebug($"FROM EXECUTE METHOD | {jobKey.Name} | {jobKey.Group} | END");
}
}
}
You may want to start running some performance counters to monitor CPU usage and memory stats and figure out what's going on.
If that doesn't lead you to any obvious answers, it's time to start profiling.
JetBrains dotTrace (Not free, 30 day trial)
Microsoft's CLR Profiler
I guess that the services requested by your service locator are not released after use. You can check that by implementing the IDisposable interface in one of your requested services and verify that Dispose() is called. I guess its not.
To fix that, you can register your services as scoped and open the scope by hand. This will ensure that all service are disposed after use.
public async Task Execute(IJobExecutionContext context)
{
// this check is superfluous and can be omitted.
// if you want to ensure that the service locator is there, check this in the constructor.
// with a good DI framework, it can't be null.
if (this.serviceProvider != null)
{
// open the scope and dispose it after use.
using (var serviceScope = host.Services.CreateScope())
{
// get the service locator from the scope.
var services = serviceScope.ServiceProvider;
try
{
JobKey jobKey = context.JobDetail.Key;
IEmailScheduleSendService emailScheduleSendServiceNew = services.GetRequiredService<IEmailScheduleSendService>();
ILogger<EmailJob> logger = services.GetRequiredService<ILogger<EmailJob>>();
logger.LogDebug($"FROM EXECUTE METHOD | {jobKey.Name} | {jobKey.Group} | START");
await emailScheduleSendServiceNew.EmailScheduleSendAsync(context);
logger.LogDebug($"FROM EXECUTE METHOD | {jobKey.Name} | {jobKey.Group} | END");
}
catch (Exception ex)
{
logger.LogDebug($"SOMETHING WENT WRONG!");
}
}
}
}
I want to use task scheduler to create thread during application start.
I made it thanks to this and this, but something goes wrong and job is not running, of course is initialized before.
My class which is run before start:
[assembly: WebActivatorEx.PreApplicationStartMethod(
typeof(Application.App_Start.TaskScheduler), "Start")]
namespace Application.App_Start
{
public static class TaskScheduler
{
private static readonly IScheduler scheduler = new StdSchedulerFactory().GetScheduler();
private static void CreateTaskToDeleteTmpFiles(Object sender)
{
scheduler.Start();
//Create job which will be add to thread
IJobDetail job = JobBuilder.Create<DeleteTmpJob>()
.WithIdentity("ClearTmpFiles")
.StoreDurably()
.Build();
//Create thread which run the job after specified conditions
ITrigger trigger = TriggerBuilder.Create()
.WithIdentity("ClearTmpFiles")
.StartAt(DateBuilder.FutureDate(1, IntervalUnit.Second))
.Build();
//Add Job and Trigger to scheduler
scheduler.ScheduleJob(job, trigger);
}
}
}
My job class:
public class DeleteTmpJob : IJob
{
private IDocumentStore documentStore;
private IUploaderCollection uploaderCollection;
public DeleteTmpJob(IDocumentStore _documentStore, IUploaderCollection _uploaderCollection)
{
documentStore = _documentStore;
uploaderCollection = _uploaderCollection;
}
public void Execute(IJobExecutionContext context)
{
documentStore.ClearTmpDirectory();
}
}
Job is not running
Anyone can help?
Have you tried using an empty constructor for your job?
"Each (and every) time the scheduler executes the job, it creates a new instance of the class before calling its Execute(..) method. One of the ramifications of this behavior is the fact that jobs must have a no-arguement constructor."
You may need to implement your own JobFactory to allow you to use DI. How you implement it depends on which library you are using.
"When a trigger fires, the JobDetail (instance definition) it is associated to is loaded, and the job class it refers to is instantiated via the JobFactory configured on the Scheduler.The default JobFactory simply calls the default constructor of the job class using Activator.CreateInstance, then attempts to call setter properties on the class that match the names of keys within the JobDataMap. You may want to create your own implementation of JobFactory to accomplish things such as having your application's IoC or DI container produce/initialize the job instance."
source: see here
I had the same problem, when I deleted constructor job worked. First try to call base constructor, if it is still not working try to delete constructor.