多个计时器的多个数据库检查

Posted

技术标签:

【中文标题】多个计时器的多个数据库检查【英文标题】:Multiple DB Checks by multiple Timers 【发布时间】:2020-08-03 10:35:37 【问题描述】:

我正在构建一个 Winforms 应用程序,该应用程序对包含敏感数据的数据库进行多项检查。

数据库正在以 24/7 的高频率更新,因此应用程序将 24/7 检查数据库。 我的最高要求是构建应用程序模块化。 这意味着如果我将来需要向应用程序添加额外的检查,我可以非常自信地添加它,我不会弄乱现有的检查。 此外,每个检查都需要能够自行启用/禁用。

我想通过为应用程序中的每个检查构建额外的复选框和计时器组合来做到这一点。 这样,任何额外的检查都是独立的(有自己的计时器和逻辑),添加新的检查不会改变现有检查中的任何一行代码。

代码(测试应用程序):

   public partial class Form1 : Form

    public Form1()
    
        InitializeComponent();
    

    private void check1CheckBox_CheckedChanged(object sender, EventArgs e)
    
        check1Timer.Enabled = check1CheckBox.Checked;
    

    private void check2CheckBox_CheckedChanged(object sender, EventArgs e)
    
        check2Timer.Enabled = check2CheckBox.Checked;
    

    private void check3CheckBox_CheckedChanged(object sender, EventArgs e)
    
        check3Timer.Enabled = check3CheckBox.Checked;
    

    private void check1Timer_Tick(object sender, EventArgs e)
    
        check1(); //check DB data
    

    private void check2Timer_Tick(object sender, EventArgs e)
    
        check2(); //check DB data
    

    private void check3Timer_Tick(object sender, EventArgs e)
    
        check3(); //check DB data
    

我有两个问题: 1. 如果在上面解释的方法中,每个检查都是独立的,没有办法打断/弄乱其他检查? 2. 添加多个同时运行的定时器(10+)的“成本”是多少,无论是稳定性/响应性/时序性?

没有一个计时器会长时间阻塞表单,每次耗时的 DB 调用都是异步的(通过 await 调用)。

在实践中:大多数检查需要以 2-5 秒的频率运行,最大频率将是每秒。 每张支票都有自己的频率和可验证性。

谢谢。

【问题讨论】:

【参考方案1】:

我能看到的唯一可能的问题是事件处理程序的重叠调用问题,这可能是一个非常严重的问题。从 .NET 平台提供的三个计时器(System.Windows.Forms.TimerSystem.Timers.TimerSystem.Threading.Timer)来看,它们都不能防止重叠,这意味着相同的查询可以在前一个查询完成之前再次发送到数据库。结果是数据库最终可能会受到比它处理能力更多的请求的轰炸。考虑到数据库有逐日变大的趋势,导致查询变得越来越慢,拥有多个具有恒定间隔的计时器可能会成为应用程序、数据库或整个系统健康的定时炸弹.

以下是一些与计时器重叠行为相关的问题,并提供了解决问题的答案:

    Synchronizing a timer to prevent overlap How to let Timer skip tick if the previous thread is still busy Timed events overlapping during execution

这是我自己对非重叠 Timer 的实现,如果我有类似的问题要解决,我会更喜欢这种行为。

/// <summary>
/// Provides a mechanism for executing a method on a thread pool thread,
/// at intervals that are automatically extended to prevent overlapping.
/// </summary>
public class NonOverlappingTimer

    private readonly object _locker = new object();
    private readonly Func<Task> _function;
    private CancellationTokenSource _cts = new CancellationTokenSource();
    private bool _enabled = false;
    private int _interval = 100;

    public NonOverlappingTimer(Action action)
    
        if (action == null) throw new ArgumentNullException(nameof(action));
        _function = () => Task.Run(action);
        AsyncLoop();
    

    public NonOverlappingTimer(Func<Task> function)
    
        if (function == null) throw new ArgumentNullException(nameof(function));
        _function = () => Task.Run(function);
        AsyncLoop();
    

    private async void AsyncLoop()
    
        while (true)
        
            CancellationToken ct;
            bool enabled;
            int interval;
            lock (_locker)
            
                ct = _cts.Token;
                enabled = _enabled;
                interval = _interval;
            
            var delayTask = Task.Delay(enabled ? interval : Timeout.Infinite, ct);
            if (enabled) await _function().ConfigureAwait(false);
            try
            
                await delayTask.ConfigureAwait(false);
            
            catch (OperationCanceledException)   // Suppress this exception
        
    

    public bool Enabled
    
        get
        
            lock (_locker) return _enabled;
        
        set
        
            CancellationTokenSource cts;
            lock (_locker)
            
                if (value == _enabled) return;
                _enabled = value;
                cts = _cts;
                _cts = new CancellationTokenSource();
            
            cts.Cancel();
        
    

    public int Interval
    
        get
        
            lock (_locker) return _interval;
        
        set
        
            if (value < 0) throw new ArgumentOutOfRangeException(nameof(value));
            CancellationTokenSource cts;
            lock (_locker)
            
                if (value == _interval) return;
                _interval = value;
                cts = _cts;
                _cts = new CancellationTokenSource();
            
            cts.Cancel();
        
    

使用示例:

var timer = new NonOverlappingTimer(async () =>

    Console.WriteLine("Tick");
    await Task.Delay(3000); // Simulate a lengthy I/O-bound operation
);
timer.Interval = 2000;
timer.Enabled = true;

输出:将每 3 秒打印一次“Tick”,尽管间隔为 2 秒。

【讨论】:

以上是关于多个计时器的多个数据库检查的主要内容,如果未能解决你的问题,请参考以下文章

C# Windows 服务 - 我的计时器不工作,获得多个线程

While 循环计时如何在带有多个线程的 C# 中工作?

页面显示多个计时器,有开始暂停按钮来控制倒计时的开关????求大神指点

多个计时器已过问题

openGL中的多个计时器[关闭]

高速处理列表的多个计时器导致问题