How can I get a Hangfire job's end time? - c#

Given a Hangfire job's ID, how can I get the time at which the job finished running?
I've tried the below, but the JobData class doesn't have a property for job end time.
IStorageConnection connection = JobStorage.Current.GetConnection();
JobData jobData = connection.GetJobData(jobId);

I have had a similar requirement before. Here is a method I wrote to get the SucceededAt property using the name of the running method and the current PerformContext:
public static DateTime? GetCompareDate(PerformContext context, string methodName)
{
return long.TryParse(context.BackgroundJob.Id, out var currentJobId)
? JobStorage.Current
?.GetMonitoringApi()
?.SucceededJobs(0, (int)currentJobId)
?.LastOrDefault(x => x.Value?.Job?.Method?.Name == methodName).Value?.SucceededAt
: null;
}
You could just as easily get DeletedJobs, EnqueuedJobs, FailedJobs, etc.
You can call it from a job method like this:
public async Task SomeJob(PerformContext context, CancellationToken token)
{
⋮
var compareDate = GetCompareDate(context, nameof(SomeJob));
⋮
}
You just have to add the PerformContext when adding the job by passing in null:
RecurringJobManager.AddOrUpdate(
recurringJobId: "1",
job: Job.FromExpression(() => SomeJob(null, CancellationToken.None)),
cronExpression: Cron.Hourly(15),
options: new RecurringJobOptions
{
TimeZone = TimeZoneInfo.Local
});
Note: It will only work if the succeeded job has not expired yet. Successful jobs expire after one day - if you need to keep them longer (to get the SucceededAt property), here is a reference for that: How to configure the retention time of job?

You could try using the Stopwatch class when you first call the task and stop it when the task is completed.
Then you could use a log nugget to generate a txt log file containing the start time and end time of your job. Or directly save this values to your database so that you can review them later.

Related

How To Create an Action Class?? To register with "WPF" task schedule queue using MVVM pattern

I am creating a WPF application. I am implementing the MVVM pattern. I have a UI which returns the source path, destination path and a list of DateTimes which is a queue of times to execute for then next seven days.
In this function I want to be able to take the list of scheduled dates and register a task using my Schedule Helper Class
This is my ViewModel which has access to my Directory Helper class instance and a model instances bound to my UI.
Below is my function which get called after my fields are populated and the button is submitted
//THIS FUNCTION IS BOUND TO A BUTTON ON THE PAGE
private void startBackUp(object param) {
// LIST OF DATETIMES
List<DateTime> backUpQueue = new List<DateTime>();
// FUNCTION IN THIS VIEW MODEL THAT RETURNS
// CURRENT INSTANCE OF THE MODEL WITH THE USERS SELECTION
backUpQueue = populateBackUpQueue(backUpQueue);
// THIS IS THE ACTION I WANT TO PERFORM
// FRUNCTION FROM INSTANCE OF A HELPER CLASS
DirectoryCommand.DirectoryCopy(BackUp.SourcePath,BackUp.DestinationPath, true);
}
I want to create a task which runs Directory copy function using this scheduled Task helper method
SchedualeTask.cs
using Microsoft.Win32.TaskScheduler;
using System;
namespace BackUpProject.Helper
{
class SchedualeWindowsTasks
{
public TaskService ts { get; set; }
public SchedualeWindowsTasks()
{
// INSTANCE OF A TASK SERVICE
TaskService ts = new TaskService();
}
private void CreateNewWindowsScheduale(Action<DateTime>schedualedAction) {
// Create a new task definition and assign properties
TaskDefinition td = ts.NewTask();
td.RegistrationInfo.Description = "THIS SHOULD BE MY TASK";
// This will be the date time to execute this task
// I can the use DailyTrigger to repeat weekly
//Currently exceute every other day
td.Triggers.Add(new DailyTrigger(2));
// This needs to be my DirectoryCopy() function with parameters
td.Actions.Add(new ExecAction("notepad.exe", "c:\\test.log", null));
// need to register it so can delete by calling DeleteTask("Test")
ts.RootFolder.RegisterTaskDefinition(#"Test", td);
}
}
}
I simply want to take by source path and directory path and execute my directoryCopy function on every DateTime given by the user and then use a weekly Trigger to repeat every week until Deleted by the user, but I handle delete later.
My question is by maintaining the MVVM pattern what is the favorable method of implementing such a task.

Can Hangfire Handle Changes to Scheduled Tasks Without Redeployment

I have been playing around with Hangfire in a Microsoft MVC application. I have gotten it to compile and schedule fire-and-forget tasks, but I am surprised that I cannot add/remove jobs while the program is running. Is it true that Hangfire cannot dynamically schedule tasks during runtime? Is there a well-known framework that allows one to schedule tasks even after the application has been compiled or deployed without having to change the C# code every time I want to add tasks?
I have also researched Quartz.NET, and it seems to have the same issue.
EDIT:
Windows Task Scheduler can allow tasks to be scheduled with a GUI, and UNIX's cron can have tasks added or removed by editing a file, but I'm looking for some sort of application running on Windows that would allow the user to add or remove tasks after the application has been deployed. I do not want to re-compile the application every time I want to add or remove tasks.
As asked, the question seems to rest on a misunderstanding of the meaning of "dynamic...during runtime". The answer is "yes," it can change tasks without redeployment (but that doesn't appear to be what your really looking for).
Hangfire will add a dashboard UI to your application if you configure it to do so, but it is not an end-to-end task management application itself. It is designed to give your application the ability to schedule work, and have that work completed in a very disconnected way from the point of invocation--it may not even be completed on the same machine.
It is limited to invoking .NET code, but by definition this fulfills your stated requirement to "dynamically schedule tasks during runtime." This can be done in response to any event within your application that you like. Tasks can be also be removed, updated and cancelled.
(Post-edit) You're correct: any scheduling UI or deserialization of task-file format you'll have to write yourself. If you are looking for a tool that gives you a UI and/or task-file OOTB, you may need to move up to a commercial product like JAMS. (Disclaimer: this may not even itself have the capabilities you require--I don't have direct experience with the product but folks I've worked with have mentioned it in a positive light).
Create an API to schedule jobs dynamically after runtime. Your API can accept input via an HTTP Get/Put/Post/Delete etc, then run an instance of anything within your code upon the API call, using the data that you give it.
For example, say you have a hard coded Task A and Task B in your code and you want to schedule them to run dynamically using different parameters. You can create an API that will run the desired task at the specified time, using the parameters that you choose.
[HttpPost]
public IHttpActionResult Post([FromBody]TaskDto task)
{
var job = "";
if(task.TaskName == "TaskA"){
job = BackgroundJob.Schedule(() => RunTaskA(task.p1,task.p2), task.StartTime);
}
if(task.TaskName == "TaskB"){
job = BackgroundJob.Schedule(() => RunTaskB(task.p1,task.p2), task.StartTime);
}
if(!string.IsNullOrWhiteSpace(task.ContinueWith) && !string.IsNullOrWhiteSpace(job)){
if(task.ContinueWith == "TaskB"){
BackgroundJob.ContinueWith(job, () => RunTaskB(task.p3,task.p4));
}
if(task.ContinueWith == "TaskA"){
BackgroundJob.ContinueWith(job, () => RunTaskA(task.p3,task.p4));
}
}
return Ok(job)
}
Then you can call the API using a JSON POST (example using javascript)
// Sending JSON data to start scheduled task via POST
//
var xhr = new XMLHttpRequest();
var url = "https://www.example.com/api/scheduletask";
xhr.open("POST", url, true);
xhr.setRequestHeader("Content-type", "application/json");
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
var json = JSON.parse(xhr.responseText);
}
};
var data = JSON.stringify({"TaskName": "TaskA", "ContinueWith": "TaskB",
"StartTime": "2-26-2018 10:00 PM", "p1": "myParam1", "p2": true,
"p3": "myParam3", "p4": false});
xhr.send(data);
And for completeness of the example here is the TaskDto class for this example
public class TaskDto
{
public string TaskName { get; set; }
public string ContinueWith { get; set; }
public DateTime StartTime { get; set; }
public string p1 { get; set; }
public bool p2 { get; set; }
public string p3 { get; set; }
public bool p4 { get; set; }
}

How to get JobKey / JobDetail of Quartz Job

I'm having trouble to understand how I can get the details of a job with Quartz version 2.3.2.
Until now, we used Quartz v1.0.x for jobs and I have to upgrade it to the latest version.
This is how we used to get the details of a job:
JobDetail job = scheduler.GetJobDetail(task.Name, groupName);
With version 2.3.2, the method GetJobDetail() doesn't have a constructor that takes 2 parameter anymore... instead, it takes a JobKey parameter.
Unfortunately I couldn't find a way to get a single JobKey.
What I tried is the following:
string groupName = !string.IsNullOrEmpty(task.GroupNameExtension) ? task.GroupNameExtension : task.GroupName;
var jobkeys = quartzScheduler.GetJobKeys(GroupMatcher<JobKey>.GroupContains(groupName));
var jobkey = jobkeys.Single(x => x.Name == task.Name);
var jobDetail = quartzScheduler.GetJobDetail(jobkey);
Is this the correct way to implement it / get the jobKey? (will there always be only one jobkey on the line var jobkey = jobkey.Single(...)?
Is there really no way to get a JobDetail without getting all the JobKeys first?
Is this the way Quartz wants us to get the JobDetail or is there a better / simpler way?
Thanks in advance
You can just create a new job key (which is just a fancy storage for a job name and a group name)
new JobKey("jobName", "jobGroupName");
As long as your job name and job group name is the same with which you created your job, you will be able to get your job detail.
var jobDetail = quartzScheduler.GetJobDetail(new JobKey("jobName", "jobGroupName"));
personnally, i implement a static method in my job class to centralise the job key creation so i don't have many litterals all over the place :
public static JobKey GetJobKey(EntityServer server)
{
return new JobKey("AutoRestart" + server.Id, "AutoRestart");
}
Note that it is also true for the triggerKey
public static TriggerKey GetTriggerKey(EntityServer server)
{
return new TriggerKey("AutoRestart" + server.Id, "AutoRestart");
}

How to persist data in a Quartz.net job Trigger between executions?

There are a couple SO articles about this, but only one directly addresses this issue. However, the solution doesn't make sense for me. I'm using a straight string.
UPDATE:
This:
[PersistJobDataAfterExecution]
public class BackgroundTaskTester : IJob
{
public void Execute(IJobExecutionContext context)
{
Debug.WriteLine("Value Is: " + context.Trigger.JobDataMap["field1"] as string);
context.Trigger.JobDataMap["field1"] = DateTimeOffset.Now.ToString();
}
}
Outputs This:
Value Is:
Value Is:
Value Is:
Value Is:
Value Is:
Value Is:
Value Is:
But this:
[PersistJobDataAfterExecution]
public class BackgroundTaskTester : IJob
{
public void Execute(IJobExecutionContext context)
{
Debug.WriteLine("Value Is: " + context.JobDetail.JobDataMap["field1"] as string);
context.JobDetail.JobDataMap["field1"] = DateTimeOffset.Now.ToString();
}
}
Outputs This:
Value Is: 10/6/2014 9:26:23 AM -05:00
Value Is: 10/6/2014 9:26:28 AM -05:00
Value Is: 10/6/2014 9:26:33 AM -05:00
However, I want to store things in the Trigger. How do I get the Trigger to persist?
ORIGINAL QUESTION:
I have a class:
[PersistJobDataAfterExecution]
public class BackgroundTaskNotification : IJob
{
public void Execute(IJobExecutionContext context)
{
<see below>
}
}
The following code doesn't function as expected:
public void Execute(IJobExecutionContext context)
{
string x = context.MergedJobDataMap["field1"];
context.Put("field1", "test string");
string y = context.MergedJobDataMap["field1"];
// PROBLEM: x != y
}
I've tried context.JobDetail.JobDataMap.Put() and also context.Trigger.JobDataMap.Put() None of them update MergedJobDataMap.
Maybe that's OK though. There is a JobDataMap on the JobDetail object and the Trigger. What I'm trying to do is this:
public void Execute(IJobExecutionContext context)
{
string x = context.MergedJobDataMap["field1"]; //get last x
<do something with x>
context.Put("field1", x); //save updated x
}
I'm trying to do something with x and have x persist between runs.
I'm not sure if it's relevant, but I'll add that when I create the job, I actually put field1 into the trigger's JobDataMap. This is because I have a single Job, and multiple triggers. I want the data to be stored at the tigger level.
Original Answer
MergedDataMap is a combination of the TriggerDataMap and the JobDataMap (with trigger entries overriding job entries). Updating this will do nothing, since it doesn't propagate changes back to the original JobDataMap or TriggerDataMap, and it's only JobDataMap that is re-persisted.
You want to set context.JobDetail.JobDataMap["field1"] in order for it to be persisted.
Update 1 (based on question edit):
If you want to save to the Trigger datamap, you do have to do a little more work.
If you look in the IJobExecutionContext you are given in Execute(), you have an instance of the scheduler that started the job, and an instance of the trigger that started the job. Combine with info here:
Update Quart.NET Trigger
to update the trigger as part of the job execution. Note that this updates the trigger immediately, not after the job run (as when Quartz manages your job data for you).
This can be adapted to work for the job's data map as well, and have the changes persisted immediately vs. automatically at the end of the job execution.

How to schedule custom task through c#

I am using Task Scheduler for scheduling my task in c# application. I think i got the basic understanding of this library.
But now i stuck in a place where i want to create a custom action which will execute on the set schedule.Like the built-in action i.e EmailAction ( Which will send mail on set schedule ), ShowMessageAction ( Which will show alert message on set schedule ), i want to create an action which will run my c# code and that code will save some data to my database.
What I tried yet is: I created a class CustomAction which inherits Action, like :
public class NewAction : Microsoft.Win32.TaskScheduler.Action
{
public override string Id
{
get
{
return base.Id;
}
set
{
base.Id = value;
}
}
public NewAction()
{
}
}
And here is my task scheduler code :
..
...
// Get the service on the local machine
using (TaskService ts = new TaskService())
{
// Create a new task definition and assign properties
TaskDefinition td = ts.NewTask();
td.RegistrationInfo.Description = "Does something";
// Create a trigger that will fire the task at this time every other day
TimeTrigger tt = new TimeTrigger();
tt.StartBoundary = DateTime.Today + TimeSpan.FromHours(19) + TimeSpan.FromMinutes(1);
tt.EndBoundary = DateTime.Today + TimeSpan.FromHours(19) + TimeSpan.FromMinutes(3);
tt.Repetition.Interval = TimeSpan.FromMinutes(1);
td.Triggers.Add(tt);
// Create an action that will launch Notepad whenever the trigger fires
td.Actions.Add(new NewAction()); <==========================
// Register the task in the root folder
ts.RootFolder.RegisterTaskDefinition(#"Test", td);
// Remove the task we just created
//ts.RootFolder.DeleteTask("Test");
}
...
....
On the line (pointed by arrow) i am getting the exception :
value does not fall within the expected range task scheduler
I am not sure what i am trying to achieve is even possible or not, if it is possible than please guid me on the correct direction?
According my understanding of your question. I had implemented same thing, but I had used "Quartz" Scheduler instead of "Task Scheduler". It is very easy to implement. May be you can also try with this.
To reference:
http://quartznet.sourceforge.net/tutorial/
Please correct me if I am wrong.

Categories