patterncsharpMinor
Writing C#'s analog of setTimeout, setInterval and clearInterval
Viewed 0 times
clearintervalsettimeoutwritingsetintervalandanalog
Problem
I got tired of starting timer every time I need to do something simple as running some function 5 seconds after something in my code happens. So I tried to write a function (actually, 3 of them:
It is working, at least for the cases I tested.
I understand that maybe something in that code is not right, maybe I forgot about something. Should I improve anything in my approach?
```
using System;
using System.Windows.Forms;
namespace timeouts_test
{
public partial class Form1 : Form
{
private Timer[] timerList = new Timer[100];
private int timerMaxIndex = -1;
public Form1()
{
InitializeComponent();
int aInterval = SetInterval(500, a);
int bInterval = SetInterval(500, b);
int cInterval = SetInterval(500, c);
SetTimeout(3500, () =>
{
ClearInterval(aInterval);
});
SetTimeout(7000, () =>
{
ClearInterval(bInterval);
});
SetTimeout(10500, () =>
{
ClearInterval(cInterval);
});
}
public void a()
{
label1.Text += "a";
}
public void b()
{
label1.Text += "b";
}
public void c()
{
label1.Text += "c";
}
public int SetInterval(int time, Action function)
{
Timer tmr = new Timer();
tmr.Interval = time;
tmr.Tick += new EventHandler(delegate (object s, EventArgs ev)
{
function();
});
tmr.Start();
timerMaxIndex++;
var index = timerMaxIndex;
timerList[timerMaxIndex] = tmr;
return index;
}
public int SetTimeout(int time, Action function)
setTimeout, setInterval and clearInterval) to write one line of code when I need it instead of, say, five.It is working, at least for the cases I tested.
I understand that maybe something in that code is not right, maybe I forgot about something. Should I improve anything in my approach?
```
using System;
using System.Windows.Forms;
namespace timeouts_test
{
public partial class Form1 : Form
{
private Timer[] timerList = new Timer[100];
private int timerMaxIndex = -1;
public Form1()
{
InitializeComponent();
int aInterval = SetInterval(500, a);
int bInterval = SetInterval(500, b);
int cInterval = SetInterval(500, c);
SetTimeout(3500, () =>
{
ClearInterval(aInterval);
});
SetTimeout(7000, () =>
{
ClearInterval(bInterval);
});
SetTimeout(10500, () =>
{
ClearInterval(cInterval);
});
}
public void a()
{
label1.Text += "a";
}
public void b()
{
label1.Text += "b";
}
public void c()
{
label1.Text += "c";
}
public int SetInterval(int time, Action function)
{
Timer tmr = new Timer();
tmr.Interval = time;
tmr.Tick += new EventHandler(delegate (object s, EventArgs ev)
{
function();
});
tmr.Start();
timerMaxIndex++;
var index = timerMaxIndex;
timerList[timerMaxIndex] = tmr;
return index;
}
public int SetTimeout(int time, Action function)
Solution
Right now your timer methods have dependencies on stuff that shouldn't be.
You're relying on
Your helper methods are relying on variables that aren't in their scope (The
To do so, we need a way to stop the timer. We could return the timer, but that might be a little too much for what we want to do. So let's create an interface,
Now, we need something to wrap our
So far so good. That deals with the problem of the
Next, let's see how we can change your implementation using
Point one, not totally related to the new implementation but very related to your old one. Using
Becomes :
So, let's look at that timer! This
Notice that the method is now
I changed the
I'm copying the
You're screwed. So copying the
Now the
Notice something? Well, that's the same code, with one different variable in
That's the final version in my opinion. I think it looks better. Though there's a final problem.
You're relying on
System.Windows.Form. That's blocking you if you ever want to use these methods in a console application or well... stuff that isn't related to WinForms. Check for the System.Timers.Timer class!Your helper methods are relying on variables that aren't in their scope (The
Timer[]). That's a dependency you want to remove.To do so, we need a way to stop the timer. We could return the timer, but that might be a little too much for what we want to do. So let's create an interface,
IInterruptable.public interface IInterruptable
{
void Stop();
}Now, we need something to wrap our
Timer so we can implement the interface.public class TimerInterrupter : IInterruptable
{
private readonly Timer _timer;
public TimerInterrupter(Timer timer)
{
if (timer == null) throw new ArgumentNullException(nameof(timer));
_timer = timer;
}
public void Stop()
{
_timer.Stop();
}
}So far so good. That deals with the problem of the
Timer[].Next, let's see how we can change your implementation using
System.Timers.Timer.Point one, not totally related to the new implementation but very related to your old one. Using
delegate is a little old-school. Since C#... 3? There are Functions you can use, that are backward compatible to delegate. That means ://Syntax might not be good, but you get the point
fooBar.SomeEvent += new delegate(object,object){asd();};Becomes :
fooBar.SomeEvent += (a,b) => asd();So, let's look at that timer! This
Timer class as an AutoReset property, which well... auto-resets the timer at the end of each interval is set to true. Which will be the main difference between your SetInterval and SetTimeout methods.public static IInterruptable SetInterval(int interval, Action function)
{
Action functionCopy = (Action)function.Clone();
Timer timer = new Timer { Interval = interval, AutoReset = true };
timer.Elapsed += (sender, e) => functionCopy();
timer.Start();
return new TimerInterrupter(timer);
}Notice that the method is now
static, since it's an helper method. You'll be able to use it in any context, which is great!I changed the
time parameter to interval, as it's clearer this way. You didn't pass a time, you passed a time interval as a parameter. It's important to be very clear.I'm copying the
Action. I'm not 100% sure that's useful, but it's to avoid a problem with references. If for example I did :Action someFunction = CreateAFunctionThatIsntNull();
SetInterval(1,someFunction);
someFunction = null;You're screwed. So copying the
Action has a purpose here.Now the
SetTimeout :public static IInterruptable SetTimeout(int interval, Action function)
{
Action functionCopy = (Action)function.Clone();
Timer timer = new Timer { Interval = interval, AutoReset = false };
timer.Elapsed += (sender, e) => functionCopy();
timer.Start();
return new TimerInterrupter(timer);
}Notice something? Well, that's the same code, with one different variable in
AutoReset. So let's take care of that by extracting another method :public static class TimerHelper
{
public static IInterruptable SetInterval(int interval, Action function)
{
return StartTimer(interval, function, true);
}
public static IInterruptable SetTimeout(int interval, Action function)
{
return StartTimer(interval, function, false);
}
private static IInterruptable StartTimer(int interval, Action function, bool autoReset)
{
Action functionCopy = (Action)function.Clone();
Timer timer = new Timer { Interval = interval, AutoReset = autoReset };
timer.Elapsed += (sender, e) => functionCopy();
timer.Start();
return new TimerInterrupter(timer);
}
}That's the final version in my opinion. I think it looks better. Though there's a final problem.
SetTimeout and SetInterval are terrible method names. I know the idea is to copy the Javascript functions, but let's make an exception. These methods names are not intuitive and overall pretty terrible. Maybe something like ExecuteEvery instead of SetInterval and ExecuteIn instead of SetTimeout would be better names.Code Snippets
public interface IInterruptable
{
void Stop();
}public class TimerInterrupter : IInterruptable
{
private readonly Timer _timer;
public TimerInterrupter(Timer timer)
{
if (timer == null) throw new ArgumentNullException(nameof(timer));
_timer = timer;
}
public void Stop()
{
_timer.Stop();
}
}//Syntax might not be good, but you get the point
fooBar.SomeEvent += new delegate(object,object){asd();};fooBar.SomeEvent += (a,b) => asd();public static IInterruptable SetInterval(int interval, Action function)
{
Action functionCopy = (Action)function.Clone();
Timer timer = new Timer { Interval = interval, AutoReset = true };
timer.Elapsed += (sender, e) => functionCopy();
timer.Start();
return new TimerInterrupter(timer);
}Context
StackExchange Code Review Q#113596, answer score: 4
Revisions (0)
No revisions yet.