Home > front end >  Using async/await instead of timer
Using async/await instead of timer

Time:02-05

I sometimes face this scenario: imagine I have some sort of triggering event occurring at a certain frequency (it could be everything, the user pressing a button again and again, or a particular data packet coming from the network, from a serial port or whatever). The application has to respond in some way to such events but the response has to happen at a certain maximum frequency, say once per second for example. I used to realize this pattern with a timer (System.Timers.Timer). Each triggering event handler just keeps starting the timer every time, and the Timer_Elapsed event handler just produces the response. Something like this:

namespace WinFormsApp1
{
    public partial class Form1 : Form
    {
        System.Timers.Timer timer = new System.Timers.Timer(1000);

        public Form1()
        {
            InitializeComponent();

            timer = new System.Timers.Timer(1000);
            timer.AutoReset = false;
            timer.Elapsed  = Timer_Elapsed;
        }

        private void Timer_Elapsed(object? sender, System.Timers.ElapsedEventArgs e)
        {
            System.Diagnostics.Debug.WriteLine(
                $"{DateTime.Now.ToString("HH:mm:ss.ff")} Response Action");
        }

        private void button1_Click(object sender, EventArgs e)
        {
            timer.Start();
            System.Diagnostics.Debug.WriteLine(
                $"{DateTime.Now.ToString("HH:mm:ss.ff")} Trigger Event");
        }
    }
}

See an example output:

17:32:38.47 Trigger Event
17:32:38.68 Trigger Event
17:32:38.88 Trigger Event
17:32:39.08 Trigger Event
17:32:39.29 Trigger Event
17:32:39.46 Trigger Event
17:32:39.50 Response Action
17:32:39.65 Trigger Event
17:32:39.85 Trigger Event
17:32:40.06 Trigger Event
17:32:40.27 Trigger Event
17:32:40.46 Trigger Event
17:32:40.66 Response Action
17:32:40.66 Trigger Event
17:32:40.88 Trigger Event
17:32:41.10 Trigger Event
17:32:41.31 Trigger Event
17:32:41.54 Trigger Event
17:32:41.67 Response Action
17:32:41.81 Trigger Event
17:32:42.60 Trigger Event
17:32:42.81 Response Action
17:32:44.42 Trigger Event
17:32:44.80 Trigger Event
17:32:45.43 Response Action
17:32:48.34 Trigger Event
17:32:49.34 Response Action

Consider that I'm not seeking for precision here, and assume that all triggering events happen on the same thread.

I was wondering how to realize such a behavior using only async and await and the Task.Delay static method or if there's a better or standard way of doing this.

CodePudding user response:

You can run your code inside an action. Then create a task that will run every second, and you can send a cancellationtoken to cancel it.

public async Task Run(Action action, int delayInSeconds){
   
    while(true){
         action();
         await Task.Delay(delayInSeconds * 1000);
   }

   public async void main(){
   
      await Run(()=>{
            //put your code here...
      });
   }

or

public void main(){

  Task.Run(async ()=>{
     await Run()=>{
          //put your code here...
     }
   }
}

CodePudding user response:

Your code doesn't satisfy this requirement:

the response has to happen at a certain maximum frequency, say once per second for example.

To satisfy that requirement, then it should happen immediately if more than one second has elapsed since the last time. But your code is always waiting one second.

You can do this without a Timer by using a Task that you assign a delay of whatever length you want. Then, on the next click, await that Task to ensure at least that amount of time has passed. If the delay has already completed, then it won't wait at all.

For example:

namespace WinFormsApp1
{
    public partial class Form1 : Form
    {
        // Start with a completed task so the first use doesn't wait
        private Task _delayTask = Task.CompletedTask;

        public Form1()
        {
            InitializeComponent();
        }

        private async void button1_Click(object sender, EventArgs e)
        {
            // If it hasn't been one second since the last click, then don't do anything
            if (!_delayTask.IsCompleted) return;

            // Restart the delay task
            _delayTask = Task.Delay(1000);
            
            Debug.WriteLine($"{DateTime.Now.ToString("HH:mm:ss.ff")} Trigger Event");
        }
    }
}

CodePudding user response:

Starting from Gabriel Luci's answer, I came up with this:

private async void button1_Click(object sender, EventArgs e)
{
    System.Diagnostics.Debug.WriteLine(
        $"{DateTime.Now.ToString("HH:mm:ss.ff")} Trigger Event");

    if (_delayTask.IsCompleted)
    {
        _delayTask = Task.Delay(1000);
        await _delayTask;
        System.Diagnostics.Debug.WriteLine(
            $"{DateTime.Now.ToString("HH:mm:ss.ff")} Response action");
    }
}

It seems to produce the correct behavior:

18:53:38.64 Trigger Event
18:53:38.84 Trigger Event
18:53:39.05 Trigger Event
18:53:39.28 Trigger Event
18:53:39.46 Trigger Event
18:53:39.64 Trigger Event
18:53:39.70 Response action
18:53:39.82 Trigger Event
18:53:40.00 Trigger Event
18:53:40.18 Trigger Event
18:53:40.37 Trigger Event
18:53:40.57 Trigger Event
18:53:40.77 Trigger Event
18:53:40.83 Response action
18:53:40.98 Trigger Event
18:53:41.16 Trigger Event
18:53:41.37 Trigger Event
18:53:41.56 Trigger Event
18:53:41.99 Response action
18:53:43.18 Trigger Event
18:53:44.20 Response action
18:53:46.58 Trigger Event
18:53:47.59 Response action
  •  Tags:  
  • Related