IScheduledTaskHandler, method Process is not called

Topics: Core, Troubleshooting
Sep 2, 2013 at 9:05 AM
Hi, I have implemented IScheduledTaskHandler, and inside my constructor I am calling my method ScheduleNextTask(). Everytime the ScheduleNextTask is called my log is updated and a new entry is created inside the db table [Scheduling_ScheduledTaskRecord]. The issue is that the Process method is not called. I would really appreciate to have some help with this.
Sep 2, 2013 at 3:22 PM
Below is my code as well and logs. If the logic is ok then I can only assume that I found a bug :Z (Tested in Orchard 1.6 and 1.7 as well)

Logs:

2013-09-02 17:18:26,734 [9] Contrib.Test.Handlers.TestTaskHandler - TestTaskHandler.ScheduleNextTask 02/09/2013 17:19:26
2013-09-02 17:19:26,657 [7] Contrib.Test.Handlers.TestTaskHandler - TestTaskHandler.ScheduleNextTask 02/09/2013 17:20:26
2013-09-02 17:20:26,615 [7] Contrib.Test.Handlers.TestTaskHandler - TestTaskHandler.ScheduleNextTask 02/09/2013 17:21:26

Code:
public class TestTaskHandler : IScheduledTaskHandler
    {
        IScheduledTaskManager _taskManager;
        public ILogger Logger { get; set; }
        private const string TaskType = "TestTask";

        public TestTaskHandler(IScheduledTaskManager taskManager, ILoggerFactory LoggerFactory)
        {
            _taskManager = taskManager;
            Logger = LoggerFactory.CreateLogger(typeof(TestTaskHandler));
            ScheduleNextTask();
        }

        public void Process(ScheduledTaskContext context)
        {
            Logger.Warning("TestTaskHandler.Processing... " + context.Task.TaskType);
        }

        private void ScheduleNextTask()
        {
            var date = DateTime.Now.AddMinutes(1);
            _taskManager.DeleteTasks(null, a => a.TaskType == TaskType);
            _taskManager.CreateTask(TaskType, date, null);
            Logger.Warning("TestTaskHandler.ScheduleNextTask " + date.ToString());
        }
    }
Developer
Sep 2, 2013 at 6:30 PM
Edited Sep 4, 2013 at 1:08 AM
Don't call ScheduleNextTask in ctor. Task info is probably not saved in the database at all, thus not firing.
As a rule of thumb - never ever do anything more complex that assignments in ctor. Objects are constructed by Autofac, so when ctor is called the object dependency hierarchy may not be fully built yet. Besides - long-running constructors will hurt performance.

If you want to implement a recurrent task, call ScheduleNextTask as the last (or first) thing in Process(...) method instead. It'll do the trick.

In the end it should look like:
public class TestTaskHandler : IScheduledTaskHandler
    {
        IScheduledTaskManager _taskManager;
        public ILogger Logger { get; set; }
        private const string TaskType = "TestTask";

        public TestTaskHandler(IScheduledTaskManager taskManager)
        {
            _taskManager = taskManager;
            Logger = NullLogger.Instance;
        }

        public void Process(ScheduledTaskContext context)
        {
            if (context.Task.TaskType == TaskType) {
                Logger.Warning("TestTaskHandler.Processing... " + context.Task.TaskType);
                ScheduleNextTask();
            }
        }

        private void ScheduleNextTask()
        {
            var date = DateTime.Now.AddMinutes(1);
            _taskManager.DeleteTasks(null, a => a.TaskType == TaskType);
            _taskManager.CreateTask(TaskType, date, null);
            Logger.Warning("TestTaskHandler.ScheduleNextTask " + date.ToString());
        }
    }
I also removed the logger assignment. Logger property will be automatically injected by Orchard - you don't need to set this on your own. The NullLogger.Instance is set there just to prevent NREs.
Sep 3, 2013 at 9:59 AM
Hi, thank u for your kind and detail response :)

If I am not mistaken ScheduleNextTask needs to be called inside the constructor so it can create a record in db (tested, record is created in db) and knows when will be the next time my Process will be called, eitherwise how the scheduler will know when to call my task?

(A quick tutorial with some hints on how to implement IScheduledTaskHandler would be a great idea)

Regarding initialization, Logger = NullLogger.Instance; if I do it like this nothing is printed in my logs. But when I have it like this Logger = LoggerFactory.CreateLogger(typeof(TestTaskHandler)); then its ok. Can you advice why this is happening?

Also, whats is NREs.?
Developer
Sep 4, 2013 at 1:31 AM
Edited Sep 4, 2013 at 1:34 AM
Nope. Again - no need to use ctor here. You never instantiate this class by hand.

What you need to do instead is to start the task initially from external code (eg. a controller action) at some point, by calling IScheduledTaskManager.CreateTask(...).
This will start the loop. Then, each time Process is called, a task will be recreated to sustain the loop - this is the idea. Orchard has a background timer that runs every minute, checks for task records to call and calls handlers (like yours) if needed. Btw - I updated the example above to filter only the tasks you want (Orchard calls all handlers each time).

There are a few examples of implementation in Orchard code - eg. Orchard.PublishLater.Handlers.PublishingTaskHandler.

About Logger - you probably instantiated this class manually thus you didn't get the Logger property filled from IoC container. If you follow the procedure above, this will work as expected and you won't have to create your own logger instance.

And last - the most likely reason why your task was not called: use DateTime.UtcNow, not DateTime.Now. Orchard assumes that all dates are stored in UTC format.

Oh and NRE = Null Reference Exception
Sep 4, 2013 at 7:13 AM
Thank u for your detailed description, I believe this post will help a lot of people :)
Sep 4, 2013 at 9:07 AM
I have prepared a small tutorial (with Tips) about this using the above comments, I hope you don't mind pszmyd. Maybe you can have this in your documentation if you want as well. Thanks once again.

https://docs.google.com/file/d/0B95dcjfaK38gQ3FsQk16Z2pwa1E/edit?usp=sharing
Developer
Sep 4, 2013 at 4:46 PM
Glad it helped:)

No problems with the tutorial. I'll probably refine this as well and write a blog post and/or Orchard doc topic.
Apr 24 at 7:00 AM
I followed your instruction. I created task handler and constructor of task is called every minute. Process method is never called. Also I am getting timeout exception "Orchard.Tasks.BackgroundService - Error while processing background task".

Also if I tried any selector with TaskManager I am getting same exception again. For example _taskManager.DeleteTasks(null, a => a.TaskType == TaskType);

I am able to see created task in table Scheduling_ScheduledTaskRecord, after I stop process. Otherwise I am getting timeout exception and can not access it.

I am using 1.7.2 version. I tried on several instances (including newly installed) on several machines.

Than you for your help in advance.