8

我正在考虑实施一个“心跳”流程来全天执行大量重复的清理任务。

这似乎是使用命令模式的好机会,所以我有一个如下所示的界面:

   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);
        }

    }
}

除了我在操作系统课上所做的一些工作之外,我对多线程没有太多经验。但是,据我所知,我的线程都没有访问任何共享状态,所以它们应该没问题。

对于我想做的事情,这看起来像是一个“OK”的设计吗?你有什么要改变的吗?

4

5 回答 5

10

这是一个很好的开始。我们最近做了很多这样的事情,所以我可以提供一些建议。

  1. 不要将线程池用于长时间运行的任务。线程池旨在运行许多微小的任务。如果您正在执行长时间运行的任务,请使用单独的线程。如果你饿死线程池(用完所有任务),排队的所有东西都只是等待线程池线程变得可用,从而显着影响线程池的有效性能。

  2. 让 Main() 例程跟踪事情的运行时间以及每次运行下一次运行的时间。而不是每个命令都说“是的,我准备好了”或“不,我没有”,这对于每个命令来说都是一样的,只有 LastRun 和 Interval 字段,然后 Main() 可以使用它们来确定每个命令何时需要运行.

  3. 不要使用队列。虽然它看起来像是一个队列类型的操作,但由于每个命令都有自己的间隔,所以它真的不是一个普通的队列。而是将所有命令放在一个列表中,然后按最短时间对列表进行排序,以便下次运行。使线程休眠,直到需要运行第一个命令。运行该命令。通过下一个命令重新运行列表。睡觉。重复。

  4. 不要使用多个线程。如果每个命令的间隔是一分钟或几分钟,您可能根本不需要使用线程。您可以通过在同一线程上执行所有操作来简化。

  5. 错误处理。这种事情需要大量的错误处理,以确保一个命令中的问题不会导致整个循环失败,因此您可以在问题发生时对其进行调试。您可能还想决定一个命令是否应该在出错时立即重试,或者等到它的下一次计划运行,或者甚至比正常延迟更多。如果每次都发生错误,您可能还不想在命令中记录错误(经常运行的命令中的错误很容易创建巨大的日志文件)。

于 2010-01-27T02:39:48.297 回答
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>();
    }
}

如果您选择走这条路,这就是您需要编写的所有代码。如果需要,您还可以选择执行更复杂的计划依赖注入,并且日志记录是开箱即用的。

免责声明:我是 NCron 的首席程序员,所以我可能有点偏见!;-)

于 2010-01-29T23:21:57.323 回答
2

我会让你所有的 Command 类不可变,以确保你不必担心状态的变化。

于 2010-01-27T02:41:01.930 回答
2

现在微软的“并行扩展”应该是编写并发代码或执行任何线程相关任务的可行选择。它在线程池和系统线程之上提供了良好的抽象,因此您无需以命令式的方式思考即可完成任务。

在我看来,考虑使用它。顺便说一句,您的代码很干净。

谢谢。

于 2010-02-17T07:11:13.483 回答
0

running变量需要被标记为volatile好像它的状态将被另一个线程改变。

至于适用性,为什么不直接使用 Timer?

于 2010-01-27T02:32:04.470 回答