mrT
mrT

Reputation: 86

How to reset an incrementing counter every N minutes

I have a counter in my program that I want to reset to 0 every 10 minutes.

My program is expected to raises events. These events correspond to warnings due to heavy usage of resources or going out of the test scenarios in our experiments, which would require some action such as dumping some test-related data in the form of CSV and some plots in Tiff format. Tiff files will be produced every 1 minute when the event counter reaches 3.

But due to large size of Tiff files I wish not to over-produce these files. Hence, reseting the counter would ensure that only re-occurring events are followed for further action.

Without adding too much unnecessary details, the main structure of my program is as below:

class Program
{
    static void Main(string[] args)
    {
      counter = 0;

      using (an API)
      {
            // do something here, which may raise an event

            while (event)
            {
                // take an action

                counter++;  // keeps track of events raised
            }

            if (counter > 3)
            {
                // take a specific action
            }
            else
            {
                // take action B
            }

            counter = 0;    // reset counter every 10 minutes, by calling a timer or async method

            // to keep the application going
            System.Windows.Forms.Application.Run();
      }
    }

    // a timer method() // to help me reset the counter
    // or
    // an Async method ResetCounter()
}

I have attempted to start a timer by creating a timer method:

private static bool TimeCounter() 
{ 
    System.Timers.Timer _delayTimer = new System.Timers.Timer(); 
    _delayTimer.Interval = 100000;   
    _delayTimer.Enabled = true;
    // _delayTimer.Elapsed += new ElapsedEventHandler(_delayTimer_Elapsed); // attempted to use an additional method as well that could get triggered after 10 mins but it gets too complicated
    _delayTimer.Start();
    // not sure how to set this timer to return a boolean true when time elapsed, rather than calling another method
    _delayTimer.AutoReset = autoreset; 
}

private static void _delayTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) 
 { 
     ResetCounter(); // the method not created yet
 } 

But taking the timer approach, firstly, I am not sure how to get this timer to return a boolean true when the time elapses, secondly, if my API in the main method calls this method every time the program gets past the if-else conditional this may reset the timer again without ever letting the first 10 minutes timer elapse.

So coming across async-await I feel this could be a better option for me, I could may be call something like this (seen here on Stackoverflow) for resetting the counter?:

var result = Task.Run(async () => { return await myAsyncMethod(); }).Result;

I have never worked with async-await before so not sure how I could achieve the desired outcome with async-await.

Upvotes: 1

Views: 4745

Answers (4)

mbj
mbj

Reputation: 1015

If you want your counter to be reset as soon as 10 minutes have passed, regardless of what else happens to be going on at that moment, then you can keep going with your System.Timers.Timer idea. Your comments to the question suggest that this is what you want.

To make something happen when a Timer expires, you attach an event handler for the Elapsed event. I suggest using lambda expressions to create the handler as an anonymous function, like this:

_delayTimer.Elapsed += (o,e) => { counter = 0; };

Since this code references counter it needs to be in a location where counter is available. (The new ElapsedEventHandler part is unnecessary -- the compiler will automatically create the delegate for you since you're attaching to an event.)

With object initializer syntax to make your code neater, creating and configuring the Timer becomes this:

var delayTimer = new System.Timers.Timer
{
    Interval = 600000, // 10 minutes is 10 * 60 * 1000 == 600000 ms 
    AutoReset = true, // makes the timer start over automatically
};
delayTimer.Elapsed += ((o, e) => { counter = 0; });
delayTimer.Start();

Note that there's no need to explicitly set Timer's Enabled property, because the Start() method will do this for you.

Side note: A cool thing about this is that it actually doesn't matter where counter is declared (as long as it's available when the handler is created). This construct where an anonymous function references an "outside" variable results in what's called a "closure" over counter. In C#, closures make variables "shared", so that the function can access the variable even if the function is invoked from a place outside of the scope where the variable was declared. In other words, this will work even if counter is a local variable (that might be impractical for other reasons, though).

Full example (console app)

using System;
using System.Timers;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            // Declare the counter somewhere
            var counter = 0;

            // Create timer
            var delayTimer = new Timer
            {
                Interval = 5000, // Changed to 5 seconds for demo purposes 
                AutoReset = true,
            };

            // Create the event handler
            delayTimer.Elapsed += ((o, e) =>
            {
                // Print counter value before resetting
                Console.WriteLine($"[Timer elapsed] The counter has value {counter}, resetting it...");
                counter = 0;
            });

            // Start the timer
            delayTimer.Start();

            // Now simulate doing other stuff while the timer is running...
            Console.WriteLine("I'll be silently incrementing the counter at a random pace.");
            Console.WriteLine("Every five seconds, the timer event handler will reset the counter " +
                              "right after telling you how far I got.");
            var r = new Random();
            while (true)
            {
                // Sleep for a random number of milliseconds (between 0 and 999)
                var sleepLength = r.Next() % 1000;
                System.Threading.Thread.Sleep(sleepLength);

                // Increment the counter
                counter++;
            }

            // Console output example (values will be random): 

            // I'll be silently incrementing the counter at a random pace.
            // Every five seconds, the timer event handler will reset the counter right after telling you how far I got.
            // [Timer elapsed] The counter has value 11, resetting it...
            // [Timer elapsed] The counter has value 9, resetting it...
            // [Timer elapsed] The counter has value 12, resetting it...
            // [Timer elapsed] The counter has value 10, resetting it...
            // [Timer elapsed] The counter has value 9, resetting it...
            // [Timer elapsed] The counter has value 8, resetting it...
            // [Timer elapsed] The counter has value 6, resetting it...
            // [Timer elapsed] The counter has value 4, resetting it...
            // [Timer elapsed] The counter has value 14, resetting it...
        }
    }
}

Upvotes: 2

Panagiotis Kanavos
Panagiotis Kanavos

Reputation: 131219

The real question is in the comments :

These events correspond to warnings due to heavy usage of resources or going out of the test scenarios in our experiments, which would require some action such as dumping some test-related data in the form of CSV and some plots in Tiff format. Tiff files will be produced every 1 minute when the event counter reaches 3.

But due to large size of Tiff files I wish not to over-produce these files. Hence, reseting the counter would ensure that only re-occurring events are followed for further action.

Processing event streams is the realm of Reactive Extensions, available as a NuGet package. Rx works with streams of events the same way LINQ deals with data.

Assuming we have a source of events, one could execute an action if there are more than 10 warnings per 3 minutes with this code :

        var warnings = eventSource
                .Where(evt => evt.Level >= Level.Warning)
                .Buffer(TimeSpan.FromMinutes(3))
                .Where(evts => evts.Count > 10)
                .Subscribe(evts => ProcessEvents(evts));

Buffer batches events in 3 minute windows. Where() filters events just like LINQ's Where would and only allows events whose level is warning or above. Later on, it only allows batches that have more than 10 warnings.

The end result is that ProcessEvents is called only if there are more than 10 warnings in a 3 minute window.

The source is any class that implements IObservable. Events, tasks or data etc can be converted to Observables. Whatever the source of the warnings is, if it can implement the IObservable interface it can be used with Rx.

In the simplest case, a Subject can be used to implement a simple observable that only produces events when someone calls its OnNext method. It's generally frowned upon because it's somewhat like using a for loop when a LINQ query is needed, but it's useful to demonstrate how to use Rx:

var eventSource=new Subject<TestEvent>();

//Somewhere where warnings are raise 
eventSource.OnNext(new TestEvent { Level = Level.Info });
...
eventSource.OnNext(new TestEvent { Level = Level.Warning });

The Rx library provides methods that transform data, events, tasks etc into observables. For example, FromEventPattern will convert a .NET Pattern into an IObservable:

var eventSource= Observable.FromEventPattern(h => someClass.SomeEvent += h,
                                             h => someClass.someEvent -= h);

Upvotes: 0

Prasanth V J
Prasanth V J

Reputation: 1196

An alternative solution is use Thread.Timer, If you need exact interval use the logic suggested by @speschel

static int counter = 0;
        static void Main(string[] args)
        {

            Timer timer = new Timer(ResetCount, null, 0, 100000);
            while (true)
            {               
                //Event simulation
                if (Console.ReadKey().Key == ConsoleKey.Enter)
                {
                    Console.WriteLine("Event triggerd"); ;
                    counter++;
                }

                if (counter > 3)
                {
                    Console.WriteLine("Take a specific action");
                }
                else
                {
                    Console.WriteLine("Take action B");
                }

                Thread.Sleep(1000);
            }           
        }

        private static void ResetCount(object state)
        {
            counter = 0;
        }

Upvotes: 0

speschel
speschel

Reputation: 46

I would simply work with DateTime.Now

1) Save the current time whenever you reset the timer or when the code will be executed first time:

var lastReset = DateTime.Now;

2) Check if the lastReset is 10 Minutes or more ago:

if(lastReset.AddMinutes(10) <= DateTime.Now)
{
    counter = 0;
}

Upvotes: 3

Related Questions