1

I am building a Winforms Application that does multiple checks on a DB containing sensitive data.

the DB is updating in high frequency 24/7, so the application will check the DB 24/7. The highest requirement I have is to build the application modular. That implies that if I need to add additional checks to the application in the future, I can add this with high confidence that i am not messing up the existing checks. In addition, every check need to be able to Enable/Disable by itself.

I thought to do that by building additional Check Box & Timer combo for each check in the application. In that way, any additional check is independent (have its own timer and logic) and adding a new check will not change a single line of code in the existing checks.

Code (Test application):

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

I have 2 questions: 1. If in the method explained above, every check is independent and have no way to interrupt/mess with the other checks? 2. What is the "cost" of adding many timers (10+) running in the same time, either in stability/ responsiveness/ timing?

None of the timers will block the form for a long time, every time consuming DB call will be Async (by await calls).

In practice: most of the checks need to run in 2-5 seconds frequency, the maximum frequency will be every second. Every check have is own frequency and veriables.

Thank you.

Yakir Shlezinger
  • 147
  • 1
  • 13

1 Answers1

0

The only possible problem I can see, which could be a quite serious problem though, is the problem of overlapping invocations of the event handlers. From the three timers provided by the .NET platform (System.Windows.Forms.Timer, System.Timers.Timer and System.Threading.Timer), none of them prevents overlapping, meaning that the same query could be sent to the database again before the completion of the previous query. The result is that the database could end up being bombarded with more requests than it can handle. Considering that the databases have the tendency to become larger day by day, resulting to queries that become progressively slower, having multiple timers with constant intervals could be a time-bomb for the health of the app, the database, or the system as a whole.

Here are some questions related with the overlapping behavior of the timers, with answers offering solutions to the problem:

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

Here is my own implementation of a non-overlapping Timer, with the behavior I would prefer if I had a similar problem to solve.

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

Usage example:

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;

Output: a "Tick" will be printed every 3 seconds, although the interval is 2 seconds.

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104