Saturday, March 12, 2005

Impact of the event delegation on System.Timers.Timer

Discovered something interesting while working on my latest utility. Here's trimmed down version of hte code: (had already declared reqtimer as a new System.Timers.Timer();

private void btnStart_Click(object sender, System.EventArgs e)
{
reqtimer.Interval=(System.Convert.ToByte(txtDelay.Text)*1000);
reqtimer.Enabled=true;
reqtimer.Elapsed += new ElapsedEventHandler(MakeRequest);
}
private void btnStop_Click(object sender, System.EventArgs e)
{
reqtimer.Stop();
}
void MakeRequest(object source, ElapsedEventArgs e) { // Do Stuff }


My utility is designed to make a series of HTTP requests to a site, and record the response time. I ran the utility with txtDelay.text of 2 so that it would make the request once every two seconds. I then stopped it, and then restarted it with the start button. The output was this:

3/12/2005 7:30:12 PM : 302 90ms
3/12/2005 7:30:13 PM : 302 10ms
3/12/2005 7:30:15 PM : 302 0ms
3/12/2005 7:30:20 PM : 302 10ms
3/12/2005 7:30:21 PM : 302 10ms
3/12/2005 7:30:22 PM : 302 10ms
3/12/2005 7:30:23 PM : 302 10ms
3/12/2005 7:30:24 PM : 302 10ms
3/12/2005 7:30:25 PM : 302 10ms


Notice that the first three lines are correct: the timer interval was 2, but that after that I stopped it and then started it again (via the buttons, without closing the application itself) the events are occuring every second. If I stopped it and started it again, it would increase even more.

I suspected that somehow with each new start the timer was being created again (silly, I know, but I was ata a bit of a loss to explain what was occuring) . So I decided that there must be more needed that just "stop"ing it during btnStop_Click. So I added to that function to make it this:

private void btnStop_Click(object sender, System.EventArgs e)
{
reqtimer.Stop();
reqtimer.Close();
}

AND it had no effect whatsoever. So I tried disposing it each time; but on the btnStart_Click it excepted because the object was declared outside of the btnStart_Click function. So I decided to declare it within that function, but of course now btnStop_Click complained because it was now outside of scope. So I moved the declaration back to being global, and one by one started moving the lines out of btnStart_Click to see which was did it. I somehow suspected that if you set multiple intervals on the timer that it would fire on those different intervals. I moved this line out: reqtimer.Interval=(System.Convert.ToByte(txtDelay.Text)*1000); and it fixed nothing.

The winner ? Turns out it was the event handler. When I moved this line:
reqtimer.Elapsed += new ElapsedEventHandler(MakeRequest);

up to frmMain instead of inside btnStart_Click , everything started working great. Turns out that I was kind of on the right track; my btnStart_Click was indeed kind of creating a new timer so to speak; by adding a new event handler, it now had two different events to fire off on each timer.Elapsed event.

I thought it was an interesting situation, and since I found tons of sites documenting the same really basic behaviours of Timer, but nothing mentioning this particular problem, I thought I'd document it here.

So the bottom line is:

If you're going to start and stop the timer once, and only once, then this will work:

namespace Sample {
public class frmMain {
private System.Timers.Timer reqtimer = new System.Timers.Timer();
public frmMain{ //stuff }
private void btnStart_Click(object sender, System.EventArgs e)
{ reqtimer.Elapsed += new ElapsedEventHandler(MakeRequest);
reqtimer.Interval=(System.Convert.ToByte(txtDelay.Text)*1000);
reqtimer.Enabled=true; }

private void btnStop_Click(object sender, System.EventArgs e)
{ reqtimer.Stop(); reqtimer.Close(); }

In this case, if once you use btnStop, you can't use btnStart again without creating duplicate events. Every time you click the btnStart button, a new "virtual timer" (so to speak) will be created. You can use and stop it only once.

The secret is to think of the event handler as a virtual timer. No, it's not really the timer object, but it's what controls if things happen or not. Move the event handler up to frmMain so that it only gets created once, and you can use the btnStart_Click as often as you like; it will start the timer but not create the duplicate events.

namespace Sample {
public class frmMain {
private System.Timers.Timer reqtimer = new System.Timers.Timer();
public frmMain{ //stuff;
reqtimer.Elapsed += new ElapsedEventHandler(MakeRequest); }
private void btnStart_Click(object sender, System.EventArgs e)
{ reqtimer.Interval=(System.Convert.ToByte(txtDelay.Text)*1000);
reqtimer.Enabled=true; }

private void btnStop_Click(object sender, System.EventArgs e)
{ reqtimer.Stop(); reqtimer.Close(); }


et voilia !

2 Comments:

Anonymous Anonymous said...

Sheet yo cracker; you tryin to implement the crack-ass f0-reel timing-b4s3d attacks I told yous about last time we was swillin the sauce?

How l4m3. I never did code that up me matey; been writin a new c# based proxy intercept engine to implement some behavioral changes to web stuff w/out changing app code that may be of interest to certain folks.

Yarr, matey, now I'll have to code up the timing stuffs too, before I make ya walk the plank.

-TDD

Wednesday, November 23, 2005 3:07:00 PM  
Anonymous Anonymous said...

Nopes, it was for a pinging utility I was working on, not a sploit. but let's swill the sauce again sometime soon crackhead ! As long as you promise not to publicy urinate anywhere ...

Wednesday, November 23, 2005 3:17:00 PM  

Post a Comment

<< Home