I have a Task Scheduler running in my Micronaut Application as described here. What I want is an option to shutdown this task if I receive some kind of request from the user. How can I do that?
In order to cancel a scheduled task you need to work directly with the TaskScheduler.
The following example explains cancelling of a job.
#Singleton
public class SomeBeanThatDoesScheduling {
private final TaskScheduler taskScheduler;
private ScheduledFuture<?> scheduledFuture;
public SomeBeanThatDoesScheduling(#Named(TaskExecutors.SCHEDULED) TaskScheduler taskScheduler) {
this.taskScheduler = taskScheduler;
}
// on application startup you can register your scheduled task
#EventListener
public void onStartup(StartupEvent startupEvent) {
scheduledFuture = taskScheduler.scheduleWithFixedDelay(initialDelay, interval, this::execute);
}
public void execute() {
System.out.println("The task has been executed");
}
// use this method to cancel the job
public void cancelTheJob() {
if (this.scheduledFuture != null) {
this.scheduledFuture.cancel(false);
}
}
}
Related
I created a .net core web api project. It has gotten kinda big and I want to program a "delete" operation which deletes a lot of stuff from the database. Since there are a lot of things to delete, this will be a long running process. So I thought maybe I can run this in the background and just write status updates somewhere for the user to see whats happening.
I googled this and I found BackgroundWorkerQueue and thought this might be my solution.
So I registered the service and everything and here is my method that calls it:
public class DeleteController : ControllerBase {
private readonly BackgroundWorkerQueue _backgroundWorkerQueue;
public AdminController(BackgroundWorkerQueue backgroundWorkerQueue){
_backgroundWorkerQueue = backgroundWorkerQueue;
}
public async Task<ActionResult> HugeDeleteMethod(int id)
{
// some prechecks here...
// and here I thought I'd start the background task
_backgroundWorkerQueue.QueueBackgroundWorkItem(async token =>
{
var a = _context.StatusTable.Find(id);
a.Status += "Blablablabla\n";
_context.StatusTable.Update(a);
await _context.SaveChangesAsync();
//now start doing delete operations
});
}
}
And that class looks like this:
public class BackgroundWorkerQueue
{
private ConcurrentQueue<Func<CancellationToken, Task>> _workItems = new ConcurrentQueue<Func<CancellationToken, Task>>();
private SemaphoreSlim _signal = new SemaphoreSlim(0);
public async Task<Func<CancellationToken, Task>> DequeueAsync(CancellationToken cancellationToken)
{
await _signal.WaitAsync(cancellationToken);
_workItems.TryDequeue(out var workItem);
return workItem;
}
public void QueueBackgroundWorkItem(Func<CancellationToken, Task> workItem)
{
if (workItem == null)
{
throw new ArgumentNullException(nameof(workItem));
}
_workItems.Enqueue(workItem);
_signal.Release();
}
}
There is also a DeleteService, which is also called in my startup, but I am not sure what it does:
public class DeleteService : BackgroundService
{
private readonly BackgroundWorkerQueue queue;
public NukeService(BackgroundWorkerQueue queue)
{
this.queue = queue;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
var workItem = await queue.DequeueAsync(stoppingToken);
await workItem(stoppingToken);
}
}
}
Both are added in my startup.cs:
services.AddHostedService<DeleteService>();
services.AddSingleton<BackgroundWorkerQueue>();
Well, maybe I'm going about this all wrong. This is never called it seems, the StatusTable field "Status" is always empty. So how do I do this?
You just need to subclass BackgroundService class or implement IHostedService and than register your service as hosted service.
This will run a service in the background. Than in your service you can leverage the BlockingQueue that will perform tasks only when they are added, e.g. like this:
public class MyService : BackgroundService {
private readonly BlockingCollection<long> queue;
public MyService(){
this.queue = new BlockingCollection<long>();
Task.Run(async () => await this.Execute());
}
public void AddId(long id) {
this.queue.Add(id);
}
private async Task Execute()
{
foreach (var id in this.queue.GetConsumingEnumerable())
{
... do your stuff ...
}
}
}
services.AddHostedService<MyService>();
Here is the docu: Background services in .net core
Idea is not to start job if already same job is running.
JobExplorer is simple injected in class where is scheduled method for running
public class JobClass {
private final Job job;
private final JobExplorer jobExplorer;
private final JobLauncher jobLauncher;
public JobMain(Job job,
JobLauncher jobLauncher,
JobExplorer jobExplorer) {
this.job = job;
this.jobLauncher = jobLauncher;
this.jobExplorer = jobExplorer;
}
and then it is executed
#Scheduled("0 */5 * ? * *")
public void startJob() {
JobParameters jobParameters = new JobParametersBuilder()
.addString("jobName", String.valueOf(instant.toEpochMilli()))
.toJobParameters();
jobLauncher.run(job, jobParameters);
}
This is not solution because if JVM stopped while job is running this will be same as current job running:
jobExplorer.findRunningJobExecutions("jobName")
It will find all jobs with exitCode ExitStatus.UNKNOWN.
There is 3 solutions as I see it:
Solution 1:
stop previous running not finished jobs and run new job
PROS: everything is clean, just one property
CONT: loosing current execution of current job
#Scheduled("0 */5 * ? * *")
public void startJob() {
(JobExecution jobExecution: jobExplorer.findRunningJobExecutions("jobName")) jobExecution.stop();
...
}
Solution 2
Calculate time between latest running job like it is here described and if it is any do not start new job:
https://stackoverflow.com/a/23218986/1182625
PROS: everything is clean
CONT: have to have doubled property (5 *60*1000 and "0 */5 * ? * *")
Set<JobExecution> jobExecutions = jobExplorer.findRunningJobExecutions("jobName");
if(jobExecutions.size()>1){
Long currentTime = (new Date()).getTime();
for(JobExecution execution : jobExecutions) {
if((currentTime - execution.getStartTime().getTime()) < 5*60*1000) {
return;
} else {
execution.stop();
}
}
}
Solution 3
Idea is simple to add static (to share between instances of class) volatile (to share between threads) flag which will indicate is any job currently running
PROS: just one property
CONT: needs 2 listeners, and volatile static variable which i don't know how reacted in multi-nodes environment
private static volatile boolean FINISHED = true;
and then simple add listener and FINISHED modify method:
// reset FINISHED after job is done
#AfterJob
public void afterJob() {
FINISHED = true;
}
public void setFinished() {
this.FINISHED = true;
}
And simple add:
#Scheduled("0 */5 * ? * *")
public void startJob() {
if(!FINISHED) return;
FINISHED = false;
...
}
And finally add StepListener
public MyStepListener() {
...
#AfterStep
public ExitStatus afterStep(StepExecution stepExecution) {
if(stepExecution.getExitStatus().getExitCode().equalsIgnoreCase(ExitStatus.FAILED.getExitCode())) (new JobMain()).setFinished();
return null;
}
Ok, I think I go to far with something could be KISS.
Keep It Simple & Stupid.
So, to achieve this is simple to put fixedDelay or fixedStringDelay in #Scheduled annotation if you want to use value from properties file.
#Scheduled(initialDelay = 3*60*1000, fixedDelayString ="${job.fixed_delay}")
With this I achieve that I don't have more than 1 instance of same job at same time.
I only lose that job start at exactly time (like ad midnight or...)
Idea is not to start job if already same job is running.
By design, Spring Batch will prevent that. If you try to start the same job instance while it has a running job execution, you will get a JobExecutionAlreadyRunningException.
I have one situation.
I have one Eclipse job with following code:
private class ExecutionJob extends Job {
public static final String MY_FAMILY = "myJobFamily";
public ExecutionJob(String name) {
super(name);
}
#Override
protected IStatus run(IProgressMonitor monitor) {
monitor.beginTask("executing ...... ", IProgressMonitor.UNKNOWN);
methodForExecution();
monitor.done();
return Status.OK_STATUS;
}
#Override
public boolean belongsTo(Object family) {
return family == MY_FAMILY;
}
}
And this methodForExecution() has code as below :
public void methodForExecution(){
PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().showView("view_id");
}
Now, the situation is, job opens up something like progressmonitor, and my method is trying to access UI which is actually behind this job's progressmonitor. And it gives NullPointerException as the progress monitor does not have ActiveWorkbenchWindow.
I can not use UIJob, as I have to execute this methodForExecution() asynchronously.
Can someone please help me resolving this.
The code you want to run must run in the UI thead.
If most of the work in the job is updating the UI and there is no long running non-UI code then you should use UIJob to run this. This is still scheduled as a job but the runInUIThread method is executed in the UI thread.
If you have a lot of non-UI code especially long running code then use a normal Job but you will have to use Display.asyncExec to run the method in the UI thread:
Display.getDefault().asyncExec(new Runnable()
{
#Override
public void run()
{
methodForExecution();
}
});
In Java 8 you could do:
Display.getDefault().asyncExec(this::methodForExecution);
You can also use syncExec instead of asyncExec to wait for the UI to update.
If the showView is all you want to do you could just do the asyncExec without using a Job.
I remember that we cannot kill the current running Quartz Job but we can interrupt and have a boolean check wherever is necessary whether we need to proceed further with the subsequent operations or not.
Even when we implement the InterruptableJob and call the scheduler.interrupt to interrupt the Job, the current executed job will be still running in the server.
Ex:
A named SQL query has been triggered by the job via Hibernate which takes a long time
A call has been made to a third party server where the third party server takes a long time to respond
http://neopatel.blogspot.in/2011/05/quartz-stop-job.html
http://forums.terracotta.org/forums/posts/list/3191.page
Could someone corrects my understanding and explain me how we can kill or stop the "currently" executing Job ?
you can create new abstract class called JobBase for example that implements IJob interface and insert abstract method:
public abstract void ExecuteJob(IJobExecutionContext context);
On JobBase you can implements method Execute like this
public abstract class JobBase : IJob,IInterruptableJob
{
private Thread currentThread;
private ILog logger;
public JobBase(ILog logger)
{
this.logger=logger;
}
public void Execute(IJobExecutionContext context)
{
var thread = new Thread(()=>
{
try
{
this.ExecuteJob(context);
}
catch(Exception ex)
{
this.logger.ErrorFormat("Unhandled exception {0}",ex.ToString());
}
});
thread.Start();
this.currentThread = thread;
this.currentThread.Join();
}
public abstract void ExecuteJob(IJobExecutionContext context);
public void Interrupt()
{
currentThread.Abort();
}
}
Each Job will implements JobExecute method.
public class TestJob :JobBase
{
private ILog logger;
public TeJob(ILog logger):base(logger)
{
}
public override ExecuteJob(IJobExecutionContext context)
{
}
}
Assumes that use some factory for creating a Job
For Stopping a Job you will call method scheduler.Interrupt(new JobKey(jobName));
As you told, there is no way to interrupt "brutally" a job in quartz, neither in JAVA.
You can encapsulate your job's logic in a separate Thread and run it with the ExecutorService.
Take a look to this example: https://stackoverflow.com/a/2275596/1517816
Assume your QuartzJob is the Test class and move your business logic in the Task class.
Hope it helps
I don't know why nobody mentioned this, or maybe this was not available at the time the question was asked.
There is a method called shutdown for a Scheduler instance.
SchedulerFactory factory = new StdSchedulerFactor();
Scheduler scheduler = factory.getScheduler();
The above is used to start a job like
scheduler.start();
Use a flag or something to know when to stop the job from running. Then use
scheduler.shutdown();
How I implemented my requirement:
if(flag==true)
{
scheduler.start();
scheduler.scheduleJob(jobDetail, simpleTrigger);
}
else if(flag==false)
{
scheduler.shutdown();
}
Where jobDetail and simpleTrigger are self explanatory.
Hope it helps. :)
Here is my question :-
I am in the process of building a client-server application where I post the request to the server to generate 2 XML's for me (server fetches the information from the DB and generates XML based on this fetched info). Now, once the server has created these 2 XML's, server streams back these 2 files to the client so that client can save them on their machine.
(POSTING + READING FROM THE STREAM) IS ONE JOB. Without 2nd operation i.e. reading from stream, Job is incomplete.
I have created a Job in eclipse which posts the request to the server and takes the streamed files and save them on client machine. Posting the request to the server is a asynchronous call (it will return immediately). Once call is posted and returned immediately, I start polling on the Network Pipe for any data sever has sent(in this case it is the XML's data) & write it into a file.
As you can see here that reading the XML's from the stream and writing them into a file is part of the overall main Job but, still a separate job in itself (should be run in a separate thread). If User cancels the main job, reading from the network stream should also be cancelled.
So, basically my requirement is a cacellable job which does this entire thing. Reading from the stream should be separate Thread/Job but should be inside the main Job. If user cancels the main Job, this innner Job (reading from the Job) should also get cancelled.
Can you guys suggests a clean approach for doing this?
-Ankit
You can create a mainjob and within that mainjob you can create a subjob. If the mainjob is cancelled you can delegate the cancel to the subjob.
I created a simple view with two buttons. One for starting the jobs and the other for cancelling.
package rcpexperiments;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.IJobChangeEvent;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.core.runtime.jobs.JobChangeAdapter;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.ui.part.ViewPart;
public class View extends ViewPart
{
private Job mainJob;
#Override
public void createPartControl(final Composite parent)
{
final Button button = new Button(parent, SWT.PUSH);
button.setText("Start Job");
button.addSelectionListener(new SelectionAdapter()
{
#Override
public void widgetSelected(final SelectionEvent e)
{
mainJob = new Job("Main Job")
{
private boolean canceled = false;
#Override
protected void canceling()
{
System.out.println("Cancel requested.");
canceled = true;
}
#Override
protected IStatus run(final IProgressMonitor monitor)
{
final Job subJob = createSubJob();
subJob.schedule();
canceled = false;
while (!canceled)
{
try
{
Thread.sleep(100);
}
catch (final InterruptedException e)
{
}
}
subJob.cancel();
System.out.println("Main Job is canceled.");
return Status.CANCEL_STATUS;
}
private Job createSubJob()
{
return new Job("Sub Job")
{
boolean subJobCancel = false;
#Override
protected void canceling()
{
subJobCancel = true;
}
#Override
protected IStatus run(final IProgressMonitor monitor)
{
System.out.println("Sub Job started.");
while (!subJobCancel)
{
try
{
Thread.sleep(100);
}
catch (final InterruptedException e)
{
}
}
System.out.println("Sub Job canceled");
return Status.CANCEL_STATUS;
}
};
}
};
mainJob.addJobChangeListener(new JobChangeAdapter()
{
#Override
public void done(final IJobChangeEvent event)
{
System.out.println("Job finished by " + event.getResult());
}
});
mainJob.schedule();
System.out.println("Main Job started.");
};
});
final Button cancel = new Button(parent, SWT.PUSH);
cancel.setText("Cancel");
cancel.addSelectionListener(new SelectionAdapter()
{
#Override
public void widgetSelected(final SelectionEvent e)
{
mainJob.cancel();
}
});
}
/** {#inheritDoc} */
#Override
public void setFocus()
{
}
}
I hope that is what you wanted.
It seemed to me like a bit of a hassle to define subjobs the way Micheal K. suggested. So I went looking into the Eclipse docs and found that the Job class defines a static method called createProgressGroup and can be used as follows (same doc) which does roughly the same thing:
Job parseJob, compileJob;
IProgressMonitor pm = Job.getJobManager().createProgressGroup();
try {
pm.beginTask("Building", 10);
parseJob.setProgressGroup(pm, 5);
parseJob.schedule();
compileJob.setProgressGroup(pm, 5);
compileJob.schedule();
parseJob.join();
compileJob.join();
} finally {
pm.done();
}
Please note that the IJobManager.getJobManager is deprecated.