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?
CodePudding user response:
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.