The project I am working on requires some executions to be done at a certain time. I am not sure what would be the best way to deal with this situation. The method must be able to survive server restart/maintenance. And method calls must be programmatically.
I am considering going down this path:
I could have a table in database (or even a message queue) called TaskTable which could have TaskID(PK), TaskName(varchar), TaskStatus(enum success,failed, scheduled) and TimeOfExecution. But I need a windows service that periodically polls the database for any unexecuted tasks. Problem I am facing is that: What do I use as the TaskName to save into database? Class name? class and method name ToString? And how can I convert the string back and programmatically invoke the method calls (I don’t want to have a giant switch statement)? A typtical task would look like below. So I ned to be able to get the name of the task "SendIncompleteNotification" and class name save it into database and on retrival invoke programatically
public static Task<string> SendIncompleteNotification
{
get
{
return new Task<string>
(
a => Console.WriteLine("Sample Task")
, "This is a sample task which does nothing."
);
}
}
The problem now is I am having problem saving the method/property name progrmatically.
var type = ApplicationTask.SendIncompleteNotification.GetType();
//type.Name shows "Task`1" rather than SendIncompleteNotification
Is there any better ways of dealing with this situation? Thanks!
Updated:
Sorry my head was spinning. I now realized what I did wrong was to have another method/property to return my Task. What I should have done was to have a new class inhrite from my Task. And there i can easily get the class name and save the string into db and later retireve back and invoke.
Is the database a requirement?
If not, what about a Windows Scheduled Task (they have a tendancy to "just work") which calls into a general console app. The arguments to the console app could be:
- A DLL containing the task to execute
- The name of a class implementing an interface you define
- Other arguments
This way you can put all of your tasks into one assembly, or multiple. Alternatively you could create an attribute, apply that attribute to your tasks to give them a "friendly name", and use reflection over the assembly to find classes with the matching attribute.
Edit: example:
interface ITask
{
void Execute(ExcecutionContext context);
}
[TaskName("Send Emails")
class SendEmailsTask : ITask
{
public void Execute(ExcecutionContext context)
{
// Send emails. ExecutionContext might contain a dictionary of
// key/value pairs for additional arguments needed for your task.
}
}
class TaskExecuter
{
public void ExecuteTask(string name)
{
// "name" comes from the database entry
var types = Assembly.GetExecutingAssembly().GetTypes();
foreach (var type in types)
{
// Check type.GetCustomAttributes for the TaskAttribute, then check the name
}
}
}
Edit 2: This is in answer to your code sample.
class YourClass
{
public static Task<string> SendIncompleteNotification
{
get {
return new Task<string>(
s => Console.WriteLine("Executing task... arguments: {0}", s),
"My task");
}
}
}
interface ITask
{
void Execute(object o);
}
class Task<T> : ITask
{
public Task(Action<T> action, string name)
{
Action = action;
}
public string Name { get; set; }
public Action<T> Action { get; set; }
void ITask.Execute(object o)
{
Action((T)o);
}
}
class Program
{
static void Main(string[] args)
{
// Assume that this is what is stored in the database
var typeName = typeof (YourClass).FullName;
var propertyName = "SendIncompleteNotification";
var arguments = "some arguments";
// Execute the task
var type = Type.GetType(typeName);
var property = type.GetProperty(propertyName);
var task = (ITask)property.GetValue(null, null);
task.Execute(arguments);
Console.ReadKey();
}
}
You might want to look into Windows Workflow. They are designed for long running processes, can be persisted to a database and woken up on event or timer, as far as I know.
Store the assembly FullName, and type FullName and the method name. Assuming the method signature is something predictable (like no parameters and returning void)
1) create an instance of the assembly using the static LoadFrom method of the Assembly type.
2) get a reference to the class type from your assembly using the GetType method
3) get MethodInfo instance from the type using the GetMethod method
4) create an instance of the type using Activator.CreateInstance
5) execute the method using the Invoke of the MethodInfo instance, passing in the class instance from step 4. (sorry that I'm at a public computer without a copy of VS to crank out real code but those 5 steps would do.
Also if you're using SQL 2005 consider using a SqlDependency object and getting "notified" when your talbe changes rather than polling.
Have you looked at Quartz, it works pretty well, and I'm pretty sure it implements all the features you need.