How to use a Timer to replace Thread.Sleep(...) in an Azure Worker Role?
Asked Answered
H

1

2

There's plenty of examples of people saying to use a Timer instead of Thread.Sleep(...) in an Azure Worker Role. No probs with that.

What I'm struggling to understand is how to code this.

Currently, I have the following (pseduo code)

_timer.Elapsed += (sender, args) => DoWork();

public override void Run()
{
    while(true)
    {
        DoWork();
    }
}

public void DoWork()
{
    try
    {
        _timer.Stop();

        // Now - do stuff ....

     }
     catch(....) { ... }

     _timer.Start()
}

And what happens, is that the code enters the DoWork() method once and DoesStuff(tm).. fine .. starts the timer (say .. with a 30 second interval) and then exits that method.

Then, it returns back to the main Run() method .. which is in that loop. So it immediately comes back around and enters the DoWork() method again .. instead of waiting for the timer to fire it off.

So I'm not sure how to replace any Thread.Sleep(...) with Timers.

Any clues?

Clarification

I do not want to exit the Run() method :) I'm very happy to keep looping forever. What I'm stuck with, is replacing the standard Thread.Sleep(...) call (which blocks the thread) and replace that with a Timer, which most people suggest.

Update

Please do not link or suggest that I should use cancelSource.Token.WaitHandle.WaitOne(); as a solution. That is not what I'm trying to achieve here. Please note the post title!

Hardnett answered 5/2, 2013 at 12:34 Comment(7)
How about taking DoWork() outside of infinite loop and put good old Thread.Sleep(10000) in that loop. The idea is to initialize the timer just once which you can do it in the Run() method.Residuary
Did you read the opening line about replacing Thread.Sleep(..) because it's NotGood(tm)?Hardnett
If I understand correctly, the idea is that the WorkerRole never comes out of Run() method otherwise your worker role will restart (#9562246). I guess what I'm not understanding is why is this a bad idea? Are you looking for the functionality where you want to schedule "DoStuff()" to get called every 30 seconds?Residuary
ah Sorry. OK, i don't want to exit the Run() method (so you're correct). But I don't want to use Thread.Sleep(..) to 'pause' the worker (for a while). Instead, I want to use a Timer.Hardnett
The first search result on this problem gave another SO question with an elegant enough answer where you simply kick off the timer in the "do stuff" section, flagged as duplicate question. stackoverflow.com/questions/9561746Felonious
@OskarDuveborn If the solution you are referring to is the ancelSource.Token.WaitHandle.WaitOne(); solution, that's actually not what I'm after :( I'm after a solution that leverage's Timer.Start()` and Timer.Stop() (as mentioned in the Subject and Body of the message).Hardnett
@OskarDuveborn I don't agree that it's a duplicate. The answer doesn't git the question here.Racon
C
6

I figure that if you want to solve this situation the way you outline here you will need a WaitHandle AND a Timer.

The short answer is here below. The long answer became a blog post: HowTo wait in a WorkerRole using Timer and EventWaitHandle over Thread.Sleep

I used an EventWaitHandle along with the Timer and came up with this solution:

public class WorkerRole : RoleEntryPoint
{
    Waiter waiter;

    public override bool OnStart()
    {
        waiter = new Waiter(WorkerConfiguration.WaitInterval);
        return base.OnStart();
    }

    public override void Run()
    {
        while (true)
        {
            DoWork();
            waiter.Wait();
        }
    }

    public void DoWork()
    {
        // [...]
    }
}

And here is the waiter class:

public class Waiter
{
    private readonly Timer timer;
    private readonly EventWaitHandle waitHandle;

    public Waiter(TimeSpan? interval = null)
    {
        waitHandle = new AutoResetEvent(false);
        timer = new Timer();
        timer.Elapsed += (sender, args) => waitHandle.Set();
        SetInterval(interval);
    }

    public TimeSpan Interval
    {
        set { timer.Interval = value.TotalMilliseconds; }
    }

    public void Wait(TimeSpan? newInterval = null)
    {
        SetInterval(newInterval);
        timer.Start();
        waitHandle.WaitOne();
        timer.Close();
        waitHandle.Reset();
    }

    private void SetInterval(TimeSpan? newInterval)
    {
        if (newInterval.HasValue)
        {
            Interval = newInterval.Value;
        }
    }
}
Clupeoid answered 6/2, 2013 at 22:26 Comment(2)
Oh wow! Thank you so much for this - i'll give it a go tonight, when I get home. /me bows low.Hardnett
NOTE: I also love how you have an advanced solution (on your blog) which uses time intervals :) Very kewl also!Hardnett

© 2022 - 2024 — McMap. All rights reserved.