Question
· Feb 21, 2018

Create task to run a business service just once

Hi,

I have a business service that needs to run only once a day at an specific time. It is important that the service only runs once.

I have tried the scheduler but you can only define the times (start-stop) when the service will be running...so this will not work for me as I need this to run only once.

Looking at the documentation I found "tasks". Apparently this is exactly what I need. As per documentation:

The recommended approach is to configure the business service with Pool Size = 0 and then use the Task Manager to launch a task that calls CreateBusinessService() on it and invokes ProcessInput() on the resulting service instance object. The advantage of calling a business service this way is that you call it at the time you want and it runs only once. If Ensemble happens to be down at that time, your task can register an error. 

The problem is that I have no idea how to create a new task. I can figure out how to add code to create the business service and invoke process input method...something like this:

Set tSC = ##class(Ens.Director).CreateBusinessService("service name",.tService)

If ($IsObject(tService)) {
   Set input = ##class(service class??).%New()
   Set input.Value = 22
   Set tSC = tService.ProcessInput(input,.output)
}

Anyone knows how can I create that task so I can add that code in?

Thanks

Discussion (12)0
Log in or sign up to continue

  ClassMethod  Createtask() As %Status
{

Set tStatus = $$$OK
Try 
{

Set tTaskName = "Task Name"
Set tTaskClass = "Task.Classname"
Set tTaskDesc = "Task Desc"

Set tSql="select ID from %SYS.Task where NameSpace = ? and TaskClass = ?"
Set tRS = ##class(%SQL.Statement).%ExecDirect(,tSql, $Namespace, tTaskClass)
If (tRS.%Next())
{
 "Updating Task: "_tTaskName
Set tTaskObj = ##class(%SYS.Task).%OpenId(tRS.ID)
}
Else 
{
"Creating Task: "_tTaskName
Set tTaskObj = ##class(%SYS.Task).%New()
}

Set tTaskObj.NameSpace = $Namespace
Set tTaskObj.Name = tTaskName
Set tTaskObj.TaskClass = tTaskClass
Set tTaskObj.Description = tTaskDesc
Set tTaskObj.RunAsUser = "_system"
//Run task daily at 2 am
Set tTaskObj.TimePeriod = tTaskObj.TimePeriodDisplayToLogical("Daily")
Set tTaskObj.TimePeriodEvery = 1 // Every day (7 days a week)
Set tTaskObj.DailyFrequency = tTaskObj.DailyFrequencyDisplayToLogical("Once") // Run once daily
Set tTaskObj.DailyStartTime = (60*60*2) // 2:00am

"Saving task"
Set tStatus = tTaskObj.%Save()
$$$ThrowOnError(tStatus)

"Saved task ID: "_tTaskObj.%Id()

Do $SYSTEM.Task.Resume(tTaskObj.%Id())
 
}
Catch ex {
Set tStatus = ex.AsStatus()
}
Quit tStatus
}

Thanks for your quick response.

As far as I can understand, that code is to create the task...and that can be done in ensemble as in ..."System Operation/Task Manager/New Task". The problem is that when creating the task you must specify a "Task Type" and there is no way that those types can be created easily...a part from in Studio.  In the "New Task" screen I can read the following:

For user-defined tasks you must first create a new subclass of the %SYS.Task.Definition class which will then be selectable as a 'Task type'.

Which means there is a way to create a class that creates a "Task Type" that later can be selected when creating a new task. But this is my problem. I don't know how to create that class and if needs to be stored in a specific location for ensemble to get that new type.

Thanks

Task Type in UI is nothing but the Class in which you are wrapping your code.

Class Task.Classname  Extends %SYS.Task.Definition

{

Set tSC ##class(Ens.Director).CreateBusinessService("service name",.tService)

If ($IsObject(tService)) {
   Set input ##class(service class??).%New()
   Set input.Value = 22
   Set tSC tService.ProcessInput(input,.output

}

}

Define them as properties in your task class:

Class User.Task.MessageArchive Extends %SYS.Task.Definition
{
/// Base directory for the archived files
Property BaseDir As %String [ InitialExpression = "/hsf/archive/" ];
/// The date of the 24 hour period from which the messages will be selected (midnight to midnight)
Property DaysOld As %Integer [ InitialExpression = 23 ];
/// When selected, messages received from services will be archived
Property MessagesInbound As %Boolean [ InitialExpression = 1 ];
/// When selected, messages sent to operations will be archived
Property MessagesOutbound As %Boolean [ InitialExpression = 1 ];
/// Send Notification Email
Property NotifyByEmail As %Boolean [ InitialExpression = 0 ];

These properties can then be referenced in your task's methods with the .. prefix notation.

I am actually trying to achieve task creation using Cache Terminal. Here is the bit of the script which does the work for me ..

send: Set tTaskObj = ##class(%SYS.Task).%New()
send: Set tTaskObj.NameSpace = "<NameSpace>" <CR>
send: Set tTaskObj.Name = "<tTaskName>" <CR>
send: Set tTaskObj.TaskClass = "<tTaskClass>" <CR>
send: Set tTaskObj.<subproperty>= "<subproperty>"<CR>
send: Set tTaskObj.Description = "<tTaskDesc>" <CR>
send: Set tStatus = tTaskObj.%Save() <CR>

The highlighted line is suppose that sub-property appears under the Task Type* .

But this is not doing anything helping me achieve the requirement.

You need two task objects one of %SYS.Task class and another of your concrete implementation class. Call AssignSettings
 to merge settings. Example:

Set tTaskDefClass = "User.Task.MessageArchive"

Set tTaskObj = ##class(%SYS.Task).%New()
Set tTaskObj.NameSpace = "<NameSpace>"
Set tTaskObj.Name = "<tTaskName>"
Set tTaskObj.TaskClass = tTaskDefClass
Set tTaskObj.Description = "<tTaskDesc>"

Set tTaskDefObj = $System.OBJ.New(tTaskDefClass)
Set tTaskDefObj.BaseDir = "your value"
Set tStatus = tTaskObj.AssignSettings(tTaskDefObj)
Quit:$$$ISERR(tStatus) tStatus
Kill tTaskDefObj

Set tStatus = tTaskObj.%Save()
send: zn "namespace"
...
send: set tTaskClassObj = ##class("class name").%New()
send: set tTaskClassObj.<subproperty> = "some value"
send: set tStatus = tTaskObj.AssignSettings(tTaskClassObj)
send: Kill tTaskClassObj
send: set tStatus = tTaskObj.%Save()

As I was using cache terminal this did work for me (and in my case changing namespace was needed as the class was present under that namespace otherwise it was throwing <CLASS DOES NOT EXIST>)

AWESOME!!! @ Eduard Lebedyuk, you saved the day.

And Thanks!! to both Eduard Lebedyuk & Jeffrey Drumm.

I can enjoy my weekend now.

An alternative solution that works for us is to use the "Schedule" setting to run it for 30m (to allow some leeway as the job takes a while), and then set the "Call Interval" setting to something very large like "999999". This is for an Inbound SQL adaptor. (If something goes wrong with this overnight run then we manually remove the "Schedule" setting and restart the Service. Once complete, we put back the setting ready for the next night.)