您将如何更改我用 C# 编写的 Heartbeat 流程?

发布于 2024-08-19 15:30:47 字数 1720 浏览 15 评论 0原文

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

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

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

}

最后,我的控制台应用程序在此循环中运行,寻找等待添加到线程池的任务:

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”的设计吗?有什么你想改变的吗?

I'm looking at implementing a "Heartbeat" process to do a lot of repeated cleanup tasks throughout the day.

This seemed like a good chance to use the Command pattern, so I have an interface that looks like:

   public interface ICommand
   {
       void Execute();
       bool IsReady();
   }

I've then created several tasks that I want to be run. Here is a basic example:

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

}

Finally, my console application runs in this loop looking for waiting tasks to add to the 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);
        }

    }
}

I don't have much experience with multi-threading beyond some work I did in Operating Systems class. However, as far as I can tell none of my threads are accessing any shared state so they should be fine.

Does this seem like an "OK" design for what I want to do? Is there anything you would change?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

焚却相思 2024-08-26 15:30:47

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

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

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

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

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

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

This is a great start. We've done a bunch of things like this recently so I can offer a few suggestions.

  1. Don't use thread pool for long running tasks. The thread pool is designed to run lots of tiny little tasks. If you're doing long running tasks, use a separate thread. If you starve the thread pool (use up all the tasks), everything that gets queued up just waits for a threadpool thread to become available, significantly impacting the effective performance of the threadpool.

  2. Have the Main() routine keep track of when things ran and how long till each runs next. Instead of each command saying "yes I'm ready" or "no I'm not" which will be the same for each command, just have LastRun and Interval fields which Main() can then use to determine when each command needs to run.

  3. Don't use a Queue. While it may seem like a Queue type operation, since each command has it's own interval, it's really not a normal Queue. Instead put all the commands in a List and then sort the list by shortest time to next run. Sleep the thread until the first command is needed to run. Run that command. Resort the list by next command to run. Sleep. Repeat.

  4. Don't use multiple threads. If each command's interval is a minute or few minutes, you probably don't need to use threads at all. You can simplify by doing everything on the same thread.

  5. Error handling. This kind of thing needs extensive error handling to make sure a problem in one command doesn't make the whole loop fail, and so you can debug a problem when it occurs. You also may want to decide if a command should get immediately retried on error or wait until it's next scheduled run, or even delay it more than normal. You may also want to not log an error in a command if the error happens every time (an error in a command that runs often can easily create huge log files).

左耳近心 2024-08-26 15:30:47

您可以选择使用一个为您处理所有调度和线程的框架来构建应用程序,而不是从头开始编写所有内容。开源库 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 的首席程序员,所以我可能有点偏见! ;-)

Instead of writing everything from scratch, you could choose to build your application using a framework that handles all of the scheduling and threading for you. The open-source library NCron is designed for exactly this purpose, and it is very easy to use.

Define your job like this:

class MyFirstJob : CronJob
{
    public override void Execute()
    {
        // Put your logic here.
    }
}

And create a main entry point for your application including scheduling setup like this:

class Program
{
    static void Main(string[] args)
    {
        Bootstrap.Init(args, ServiceSetup);
    }

    static void ServiceSetup(SchedulingService service)
    {
        service.Hourly().Run<MyFirstJob>();
        service.Daily().Run<MySecondJob>();
    }
}

This is all the code you will need to write if you choose to go down this path. You also get the option to do more complex schedules or dependency injection if needed, and logging is included out-of-the-box.

Disclaimer: I am the lead programmer on NCron, so I might just be a tad biased! ;-)

幸福不弃 2024-08-26 15:30:47

我将使您的所有命令类 不可变 以确保您不必担心更改陈述。

I would make all your Command classes immutable to insure that you don't have to worry about changes to state.

一抹苦笑 2024-08-26 15:30:47

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

我认为考虑使用它。顺便说一句,你的代码很干净。

谢谢。

Now a days 'Parallel Extensions' from microsoft should be the viable option to write concurrent code or doing any thread related tasks. It provides good abstraction on top of thread pool and system threads such that you need not to think in an imperative manner to get the task done.

In my opinion consider using it. By the way, your code is clean.

Thanks.

为你拒绝所有暧昧 2024-08-26 15:30:47

如果正在运行的变量的状态将被另一个线程更改,则需要将其标记为易失性。

至于是否合适,为什么不直接使用Timer呢?

running variable will need to be marked as volatile if its state is going to be changed by another thread.

As to the suitability, why not just use a Timer?

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文