C# 定期触发函数的正确方法

C# Proper way of periodically firing functions

我有大约 20 个函数,其中一些应该每秒触发一次,一些应该每秒触发 20 次,其他的一分钟触发两次目前我有这样的东西:

                DateTime nowVar = DateTime.Now;
                var lastExecutionFunc1 = nowVar;
                var lastExecutionFunc2 = nowVar;
                var lastExecutionFunc3 = nowVar;
                var lastExecutionFunc4 = nowVar;

        DateTime _now = DateTime.Now;


                if ((_now - lastExecutionFunc1).TotalSeconds >= 0.1)
                        {
                            lastExecutionFunc1 = _now;
                            //dosomething
                        }
               if ((_now - lastExecutionFunc2).TotalSeconds >= 0.5)
                        {
                            lastExecutionFunc2 = _now;
                            //do something else
                        }
               if ((_now - lastExecutionFunc3).TotalSeconds >= 30)
                        {
                            lastExecutionFunc3 = _now;
                            //do something else
                        }
.....

虽然这可行,但我不禁认为应该有一种更优雅的方法来做到这一点。创建一个 var 来存储每次执行会使核心看起来非常混乱。我猜我可以使用 pair,但这也不太好。

有什么建议吗?

编辑:如果您想了解我正在努力完成的工作,you can see the whole code here。在第 525 行。

你是否为这个定时事件写了一个无限循环?非常糟糕的做法。 如果你们中的任何一个 "do something" 持续很长时间,所有其他功能(和主程序)将停止工作。如果你的函数可以独立工作,就让它们独立工作,并为每个函数使用不同的计时器。如果某些功能是独占的,请使用信号量(静态成员)以避免并行工作。

这是一种方法:

我创建了一个 class,它使用 Action 委托将方法与其间隔绑定:

public class ActionInvoker
{
    private DateTime _LastRunTime;
    private Action _Action;
    private double _Interval;

    public ActionInvoker(Action action, double interval)
    {
        _Action = action;
        _Interval = interval;
        _LastRunTime = DateTime.Now;
    }


    public void InvokeAction()
    {
        var now = DateTime.Now;
        if ((now - _LastRunTime).TotalMilliseconds >= _Interval)
        {
            _LastRunTime = now;
            _Action.Invoke();
        }
    }

我添加了 ActionInvoker 的列表作为表单的私有成员:

private List<ActionInvoker> _Actions;

现在,在窗体的构造函数中,我已经这样做了:

_Actions = new List<ActionInvoker>();

_Actions.Add(new ActionInvoker(DoThis, 10000));
_Actions.Add(new ActionInvoker(DoThat, 3000));

现在我在 Timer.Tick 事件处理程序中只有这段代码:

foreach(var ai in _Actions)
{
    ai.InvokeAction();
}

当然,这只是一个基本的 POC,如果您想向方法发送参数,或者如果您的方法 return 值,则需要进一步开发它。

另请注意,您最好 运行 这些方法 async 以防止 UI 在完成之前冻结。

下面的代码展示了一个带有同步锁的基本调度程序。

using System;
using System.Collections.Generic;
using System.Timers;

namespace TimerUsage
{
    class Program
    {
        static void Main(string[] args)
        {
            Scheduler scheduler = new Scheduler();
            scheduler.ScheduleMethod(100, () => Console.WriteLine("func1"));
            scheduler.ScheduleMethod(200, () => Console.WriteLine("func2"));
            scheduler.ScheduleMethod(300, () => Console.WriteLine("func3"));
            scheduler.ScheduleMethod(1000, () => Console.WriteLine("func4"));
            scheduler.Run();
            System.Threading.Thread.Sleep(10000);
        }

    }

    public class Scheduler
    {
        private Dictionary<int, Row> _schedule = new Dictionary<int, Row>();

        public void ScheduleMethod(int interval, Action method)
        {
            Row row;
            if (!_schedule.TryGetValue(interval, out row))
            {
                row = new Row(interval);
                _schedule[interval] = row;
            }
            row.AddMethod(method);
        }

        public void Run()
        {
            foreach (var item in _schedule)
            {
                item.Value.StartTimer();
            }
        }
    }

    internal class Row
    {
        private object _syncLock = new object();
        private Timer _timer;
        private List<Action> _methods = new List<Action>();

        public Row(int interval)
        {
            _timer = new System.Timers.Timer(interval);
            _timer.Elapsed += ExecuteItems;
        }

        private void ExecuteItems(object sender, ElapsedEventArgs e)
        {
            lock (_syncLock)
            {
                foreach (var method in _methods)
                {
                    method();
                }
            }
        }

        public void AddMethod(Action method)
        {
            _methods.Add(method);
        }

        public void StartTimer()
        {
            _timer.Start();
        }

    }

}