2

I am trying to use System.Timers.Timer to fire events every second. Example below (Console application)

        static void Main(string[] args)
        {
            var timer = new System.Timers.Timer(1000);
            timer.Elapsed += (sender, e) => PrintToConsole();
            timer.Start();
            Console.ReadLine();
        }
       
        private static void PrintToConsole()
        {
            var randomInt = new Random().Next(1000);
            Console.WriteLine(randomInt);
            Thread.Sleep(2000);
            Console.WriteLine(randomInt);         
        }

Since there is a sleep in PrintToConsole(), the random number generated at the beginning of function wont be the next line printed, so it will be like below

12 45 56 12 67

That makes sense as the subsequent threads from elapsed event take over when one thread blocks.

I need the event handlers to honour the event order(the second handler should follow the first handler and so on). How is it possible?

Jimmy
  • 3,224
  • 5
  • 29
  • 47
  • 1
    I don't understand the question, if you understand at all what `lock(object)` means. – Damien_The_Unbeliever Sep 28 '21 at 14:38
  • 1
    Because only one thread at a time can enter the lock, the next one has to wait. Note that you will run out of threads in the thread pool very soon. – Klaus Gütter Sep 28 '21 at 14:38
  • 1
    Me: "Yo, lemme borrow your phone to order a pizza" *dials* *orders* *doesn't give back the phone*. You: "bruh. gimme my phone, I wanna order a pizza too" Me: "Not till my pizza gets here". There is no way in this situation you're going to get a pizza before mine gets here because I'm holding your phone hostage. – Wyck Sep 28 '21 at 14:40
  • What I want is the event handler honour the event firing order. I have seen the mistake I have done in the example code which is misleading. – Jimmy Sep 28 '21 at 14:43
  • I think I need to use a different timer which is single thread or use a blocking collection to ensure order. Thank you for the help guys. – Jimmy Sep 28 '21 at 14:52
  • If event can take longer that timer interval and you want to prevent overlapping - stop timer at the beginning of event handler then start again at the end – Evk Sep 28 '21 at 14:57
  • @Jimmy the problem isn't the timer. You'll have to explain what you want. There's probably no reason to use a blocking collection. – Panagiotis Kanavos Sep 28 '21 at 14:57
  • 1
    @Jimmy how do you want to handle overlapping events? Discard them? Process them immediately after the current one? – Panagiotis Kanavos Sep 28 '21 at 14:58
  • @PanagiotisKanavos. Event handler execution order should be exactly event firing order. One after other(sequential) – Jimmy Sep 28 '21 at 15:01
  • 1
    [This answer](https://stackoverflow.com/questions/30462079/run-async-method-regularly-with-specified-interval/62724908#62724908 "Run async method regularly with specified interval") may give you some ideas about how to solve this problem in a better way (the `PeriodicAsync` method). – Theodor Zoulias Sep 28 '21 at 15:04

1 Answers1

0

From the comments, it looks like the real question is how to handle events in order

What I want is the event handler honour the event firing order.

This can be done using a Channel to which the events are posted. A single worker thread can read the posted events in order:

var channel=Channel.CreateUnbounded<ElapsedEventArgs>();
var writer=channel.Writer();
var timer = new System.Timers.Timer(1000);
timer.Elapsed += (sender, e) => writer.TryWrite(e));
timer.Start();

The events posted to the channel can be processed in order through a ChannelReader<>

async ProcessEvents(ChannelReader<ElapsedEventArgs> input,
                    CancellationToken token=default)
{
    await foreach(var evt in input.ReadAllAsync(token))
    {
        Console.WriteLine($"{evt.SignalTime}");
    }
}

And called with :

await ProcessEvents(channel);

A Channel acts as an asynchronous pub/sub collection. The timer publishes events to the Channel, and the ProcessEvents subscribes to them.

One could even write an extension method to create a channel from a timer :

public static ChannelReader<ElapsedEventArgs> ToChannel(
    this Timer timer,
    CancellationToken token)
{
    var channel=Channel.CreateUnbounded();
    var writer=channel.Writer();
    timer.Elapsed+=Handler;

    token.Register(()=>{
        timer.Elasped-=Handler;
        writer.Complete();
    });
    return channel;

    void Handler(object sender, ElapsedEventArgs args)
    {
        writer.TryWrite(args);
    }
} 

This allows combining the processing steps in a pipeline:

var cts=new CancellationTokenSource();
var source=timer.ToChannel(cts.Token);
await ProcessEvents(source,cts.Token);

....
timer.Stop();
cts.Cancel();

The CancellationTokenSource is needed because there's no way to detect whether the timer has stopped or not.

Panagiotis Kanavos
  • 120,703
  • 13
  • 188
  • 236