我正在考虑实施“Heartbeat”流程,以便在一天内完成大量重复清理任务。
这似乎是使用Command模式的好机会,所以我有一个看起来像这样的界面:
public interface ICommand
{
void Execute();
bool IsReady();
}
然后我创建了几个我想要运行的任务。这是一个基本的例子:
public class ProcessFilesCommand : ICommand
{
private int secondsDelay;
private DateTime? lastRunTime;
public ProcessFilesCommand(int secondsDelay)
{
this.secondsDelay = secondsDelay;
}
public void Execute()
{
Console.WriteLine("Processing Pending Files...");
Thread.Sleep(5000); // Simulate long running task
lastRunTime = DateTime.Now;
}
public bool IsReady()
{
if (lastRunTime == null) return true;
TimeSpan timeSinceLastRun = DateTime.Now.Subtract(lastRunTime.Value);
return (timeSinceLastRun.TotalSeconds > secondsDelay);
}
}
最后,我的控制台应用程序在此循环中运行,寻找要添加到ThreadPool的等待任务:
class Program
{
static void Main(string[] args)
{
bool running = true;
Queue<ICommand> taskList = new Queue<ICommand>();
taskList.Enqueue(new ProcessFilesCommand(60)); // 1 minute interval
taskList.Enqueue(new DeleteOrphanedFilesCommand(300)); // 5 minute interval
while (running)
{
ICommand currentTask = taskList.Dequeue();
if (currentTask.IsReady())
{
ThreadPool.QueueUserWorkItem(t => currentTask.Execute());
}
taskList.Enqueue(currentTask);
Thread.Sleep(100);
}
}
}
除了我在Operating Systems类中所做的一些工作之外,我没有太多的多线程经验。但是,据我所知,我的线程都没有访问任何共享状态,因此它们应该没问题。
对于我想要做的事情,这看起来像是一个“OK”设计吗?你有什么改变吗?
答案 0 :(得分:10)
这是一个很好的开始。我们最近做了很多这样的事情,所以我可以提出一些建议。
不要将线程池用于长时间运行的任务。线程池旨在运行许多小任务。如果您正在执行长时间运行的任务,请使用单独的线程。如果你让线程池饿死(用完所有任务),排队的所有东西都会等待线程池线程变得可用,这会显着影响线程池的有效性能。
让Main()例程跟踪事情的运行时间以及每次运行的时间。而不是每个命令说“是的我准备好了”或“不是我不是”对于每个命令都是相同的,只需要具有LastRun和Interval字段,然后Main()可以用来确定每个命令何时需要运行
不要使用队列。虽然它看起来像是一个Queue类型的操作,但由于每个命令都有自己的间隔,所以它实际上不是一个普通的Queue。而是将所有命令放在List中,然后按最短时间对列表进行排序。睡眠线程,直到需要运行第一个命令。运行该命令。按下一个命令运行列表。睡觉。重复。
不要使用多个线程。如果每个命令的间隔是一分钟或几分钟,您可能根本不需要使用线程。您可以通过在同一个线程上执行所有操作来简化。
错误处理。这种事情需要广泛的错误处理,以确保一个命令中的问题不会使整个循环失败,因此您可以在问题发生时进行调试。您还可能想要确定一个命令是否应该在出现错误时立即重试,或者等到下一次计划运行,或者甚至比正常延迟更多。如果每次都发生错误,您可能还希望不在命令中记录错误(经常运行的命令中的错误很容易创建大量日志文件)。
答案 1 :(得分:6)
您可以选择使用为您处理所有调度和线程的框架来构建应用程序,而不是从头开始编写所有内容。开源库NCron就是为了这个目的而设计的,它非常易于使用。
像这样定义你的工作:
class MyFirstJob : CronJob
{
public override void Execute()
{
// Put your logic here.
}
}
为您的应用程序创建一个主入口点,包括这样的计划设置:
class Program
{
static void Main(string[] args)
{
Bootstrap.Init(args, ServiceSetup);
}
static void ServiceSetup(SchedulingService service)
{
service.Hourly().Run<MyFirstJob>();
service.Daily().Run<MySecondJob>();
}
}
如果您选择沿着这条路走下去,那么所有您需要编写的代码。如果需要,您还可以选择more complex schedules或dependency injection,并且logging包含在开箱即用状态。
免责声明:我是NCron的首席程序员,所以我可能只是有点偏颇! ; - )
答案 2 :(得分:2)
我会创建所有命令类immutable,以确保您不必担心状态更改。
答案 3 :(得分:2)
现在微软的一天'Parallel Extensions'应该是编写并发代码或执行任何线程相关任务的可行选项。它在线程池和系统线程之上提供了良好的抽象,因此您无需以强制方式思考以完成任务。
在我看来考虑使用它。顺便说一句,你的代码很干净。
感谢。
答案 4 :(得分:0)
running
变量的状态将被另一个线程更改,则需要将其标记为volatile
。
至于适用性,为什么不使用定时器?