29

I commonly employ a while loop that continues to try some operation until either the operation succeeds or a timeout has elapsed:

bool success = false
int elapsed = 0
while( ( !success ) && ( elapsed < 10000 ) )
{
     Thread.sleep( 1000 );
     elapsed += 1000;
     success = ... some operation ...     
}

I know there a couple of way to implement this, but the basic point is that I repeatedly try some operation with a sleep until success or I've slept too long in aggregate.

Is there a built-in .net class/method/etc to save me from re-writing this pattern all over the place? Perhaps input is an Func(of bool) and the timeout?

Edit
Thanks to all who contributed. I opted for the sleep() approach because it was the least complicated and I'm totally anti-complexity =) Here's my (still needs to be tested) implimentation:

 public static bool RetryUntilSuccessOrTimeout( Func<bool> task , TimeSpan timeout , TimeSpan pause )
    {

        if ( pause.TotalMilliseconds < 0 )
        {
            throw new ArgumentException( "pause must be >= 0 milliseconds" );
        }
        var stopwatch = Stopwatch.StartNew();
        do
        {
            if ( task() ) { return true; }
            Thread.Sleep( ( int )pause.TotalMilliseconds );
        }
        while ( stopwatch.Elapsed < timeout );
        return false;
    }
Suraj
  • 35,905
  • 47
  • 139
  • 250
  • You tagged with two .NET Framework versions, so which are you looking to have a solution compatible with? – BoltClock Jul 08 '11 at 19:11
  • I don't **know** of a utility that will do this for your, but you might _try_ building an extension method (maybe of 'object') ... but that may be all a little _too_ disconnected and abstract... – Cos Callis Jul 08 '11 at 19:13
  • @BoltClock - sorry, I'm on 4.0 – Suraj Jul 08 '11 at 19:23
  • All - wow! I didn't expect so many answers. bear with me while I digest it all. =) – Suraj Jul 08 '11 at 19:26

9 Answers9

44

You could use SpinWait.SpinUntil

See https://msdn.microsoft.com/en-us/library/dd449238(v=vs.110).aspx

bool spinUntil = System.Threading.SpinWait.SpinUntil(() => job.IsDisposed, TimeSpan.FromSeconds(5));
Michael Baker
  • 3,338
  • 26
  • 29
  • 10
    I don't understand why this was not marked as the answer. It is clearly the answer - better than any of the other suggestions 100%. – Jerry Nixon Jul 14 '17 at 00:51
  • It would be exactly what the doctor ordered if it _was_ capable of calling an async function as well... – Gábor Aug 24 '23 at 14:31
27

You could wrap your algorithm in a method:

public bool RetryUntilSuccessOrTimeout(Func<bool> task, TimeSpan timeSpan)
{
    bool success = false;
    int elapsed = 0;
    while ((!success) && (elapsed < timeSpan.TotalMilliseconds))
    {
        Thread.Sleep(1000);
        elapsed += 1000;
        success = task();
    }
    return success;
}

and then:

if (RetryUntilSuccessOrTimeout(() => SomeTask(arg1, arg2), TimeSpan.FromSeconds(10)))
{
    // the task succeeded
}
Darin Dimitrov
  • 1,023,142
  • 271
  • 3,287
  • 2,928
  • thanks! looks like the same solution as scottm (or vice-versa =) – Suraj Jul 08 '11 at 19:34
  • 2
    @SFun28, I have posted my solution 19 minutes ago, while @scottm posted his 18 minutes ago. Up to you to decide whose solution came first. – Darin Dimitrov Jul 08 '11 at 19:35
  • I like that your function returns the true/false result and also good function name! Thanks, Darin! – Suraj Jul 08 '11 at 20:07
5

You really should not have to use Sleep() to wait for tasks to complete. You waste an average of 500ms after the task has completed by doing this.

You ought to be able to do this deterministically using Task Parallel Library, see here for example.

This example shows how to use the Wait method, or its equivalent in the Task class, to wait on a single task. It also shows how to use the static WaitAll and WaitAny methods to wait on multiple tasks.

Steve Townsend
  • 53,498
  • 9
  • 91
  • 140
  • Do you mean the waste is attributed to the Sleep() method itself or because I might sleeping when the operation would have completed? What if the operation requires a network call (i.e. something you do not necessarily want to keep firing out without some kind of pause in-between calls? Also, there's the issue of waiting for something that doesn't complete (even with a timeout, I don't want to keep a thread in an infinite loop) – Suraj Jul 08 '11 at 19:43
  • Sleep() will wait for 1 second even if the operation completes 1ms after the Sleep() was called. That's what I meant. For waiting for possibly infinite operations, there are `Wait(timeout)` options as shown in that example and also for regular Synchronization using Events, Semaphores etc for cross-thread signalling. If the operation requires a delay between invocations, have it fire from a callback on `System.Threading.Timers.Timer` rather than hanging up your thread in `Sleep`. – Steve Townsend Jul 08 '11 at 19:48
  • Great idea about the Timer. So I get the idea of launching a task and waiting for it to complete given a timeout, but after that timeout I have to signal for the Timer to stop. And of course if the operation succeeds before the timeout I want to stop immediately. I'm not sure how I put all of this together...seems like a complicated mix of Timer class and TPL – Suraj Jul 08 '11 at 19:56
  • Good threaded code is indeed hard to write. TPL certainly makes it easier though. Code defensively (try..catch is your friend) and test extensively. Prototyping a new improved design in C# is a lot easier than native code would be. – Steve Townsend Jul 08 '11 at 19:58
4

I don't know that there's any existing thing, but I would think you could create a method to that would accept the timeout and the success-determination function. Something like this:

public static bool KeepTrying(int timeout, Func<bool> operation)
{
    bool success = false;
    int elapsed = 0;
    while ((!success) && (elapsed < timeout))
    {
        Thread.Sleep(1000);
        elapsed += 1000;
        success = operation();
    }
    return success;
}

or maybe your Function could be more "robust" and you could couple it with flexible arguments:

public bool KeepTrying(int timeout, Func<object[], bool> operation, params object[] arguments)
{
    bool success = false;
    int elapsed = 0;
    while ((!success) && (elapsed < timeout))
    {
        Thread.Sleep(1000);
        elapsed += 1000;
        success = operation(arguments);
    }
    return success;
}
Steven
  • 1,260
  • 9
  • 22
  • good idea about robustness! After all, I might want to specify arguments to my function. Wondering if there's a way that I can pre-construct the Func with arguments and take advantage of the closure? – Suraj Jul 08 '11 at 19:39
  • @SFun28 you'd have to overload the function and pass in however many arguments you want to allow. For example, look at these overloads: http://msdn.microsoft.com/en-us/library/dd402862.aspx – scottm Jul 08 '11 at 19:43
3

The first solution is to use SpinWait

SpinWait.SpinUntil(() => LoopProcedure(), 1000);

The other solution is to use Task.Wait()

var task = Task.Run(() => LoopProcedure()); 
task.Wait(1000);

Wrap your loop into a procedure that return bool value

private bool LoopProcedure()
{
   bool success = false
   while( ( !success )
   {
      // do some stuff ...
   }
   return success;
}
2

Others have mentioned thread synchronization techniques, which would allow you to wait until some task is finished. However, if you want to continue polling every second like you are doing, you can wrap that method like this:

void Main()
{
    Timeout(() => {return false;});
}

public void Timeout(Func<bool> action, int timeout)
{
    bool success = false;
    int elapsed = 0;
    while( ( !success ) && ( elapsed < timeout ) )
    {
         Thread.Sleep( 1000 );
         elapsed += 1000;
         success = action();
    }
    Console.WriteLine("timed out.");
}
scottm
  • 27,829
  • 22
  • 107
  • 159
  • just a note, even though loop times out, action continues to execute in the above code – Amit Dec 07 '17 at 22:42
1

You can abstract shorten your code a bit and generalize the Timeout:

int timer = 0;
while (!SomeOperation(...) && Timeout(ref timer, 1000, 10000));

public bool Timeout(ref int timer, int increment, int maximum)
{
    timer += increment;
    Thread.Sleep(increment);

    return timer < maximum;
}
myermian
  • 31,823
  • 24
  • 123
  • 215
0

I prefer the async-Task-approach. Many things copied, but my tested version:

async Task<bool> DelayTillCondition(Func<bool> exitCondition, int delayInMs, int timeoutInSec)
{
    bool success = false;
    int elapsed = 0;
    while ((!success) && (elapsed < timeoutInSec * 1000))
    {
        await Task.Delay(delayInMs);
        elapsed += delayInMs;
        success = exitCondition();
    }
    return success;
}

And the compact single-line method-call:

await DelayTillCondition(() => status == "Connected", 500, 30);

Consider that the int-parameter overrun is not captured here!

0

You need to use Thread.Join. From MSDN,

Blocks the calling thread until a thread terminates, while continuing to perform standard COM and SendMessage pumping.

If you want to wait until a specified time elapses, then use the Thread.Join (TimeSpan) method. .NET 3.0, 3.5, 4.0 only.

Blocks the calling thread until a thread terminates or the specified time elapses, while continuing to perform standard COM and SendMessage pumping.

This tutorial on how to use Threads in C# will help you.

  • The purpose of Thread.Join is to block the current thread till the activity which is being performed on it ends. – Hasan Fahim Jul 08 '11 at 19:16
  • @Hasan: What do you think his While loop is doing? Also, Thread.Join continues to perform standard COM and SendMessage pumping so as to not block the UI thread. –  Jul 08 '11 at 19:17
  • If you look at the article you provided. It is mentioned in it that "Use this method to ensure a thread has terminated. The caller will block indefinitely if the thread does not terminate. If the thread has already terminated when Join is called, the method returns immediately" – Hasan Fahim Jul 08 '11 at 19:24
  • @Hasan: Exactly, then there is no time to wait. What is the problem? –  Jul 08 '11 at 19:24
  • In other words, when you start a thread, the calling thread is blocked until the thread terminates. – Hasan Fahim Jul 08 '11 at 19:26
  • @Hasan: Except for COM and Windows Message Pumps. - This replicates what he is doing already with the While loop. –  Jul 08 '11 at 19:27
  • 1
    @0A0D (cool name) - Thread.Join(TimeSpan) is not the solution in itself right? I would have to launch another thread with the while loop...and although the timeout would guarantee that I move on, what happens if the launched thread is now in an infinite loop? I'm having trouble seeing the full solution here. – Suraj Jul 08 '11 at 19:33
  • @SFun28: your provided while loop has an elapsed time. It is assumed that the work will take x amount of time. If you need more time, then you should let the user know. Your worker thread can always stop after the elapsed time too. –  Jul 08 '11 at 19:35
  • @0A0D. Where is there Thread.Start, that you are calling Thread.Join? Also join is not a static function, so you need to call it on some thread object e.g newThread.Join. Besides you haven't specified where the user should use Join? You should have posted a snippet to make your answer more understandable. Anyways... – Hasan Fahim Jul 08 '11 at 19:36
  • @Hasan: Sour grapes? He wanted to know if there was a built-in way to do it. I provided it. You are free to provide your own answer. –  Jul 08 '11 at 19:37
  • :-) It's not about sour grapes. I guess we all are here for the purpose of learning and it's not bad in admitting that we don't know something. So anyways we should have a look at the right solutions that others have provided and learn what we don't know. – Hasan Fahim Jul 08 '11 at 19:47
  • @0A0D - I too am having difficulty understanding your solution. I think I need to be more clear about my goals: 1. periodically try some operation and stop if it succeeds. 2. ensure that I'm not caught in an infite loop by having a timeout. Perhaps some pseudocode would help me. – Suraj Jul 08 '11 at 19:47
  • Also, I think everyone is assuming this call is happening on my main thread, but in fact this code is executing within a thread generated from the main thread, so not sure if that changes the answer? – Suraj Jul 08 '11 at 20:03
  • @SFun28: Does not change my answer. You have the possibility of two threads here. –  Jul 08 '11 at 20:04