33

How to wait x seconds or until a condition becomes true? The condition should be tested periodically while waiting. Currently I'm using this code, but there should be a short function.

for (int i = 10; i > 0 && !condition(); i--) {
    Thread.sleep(1000);
}
giannis christofakis
  • 8,201
  • 4
  • 54
  • 65
user3561614
  • 1,024
  • 1
  • 12
  • 20
  • Who will set `condition` to `true`? –  Aug 15 '14 at 11:15
  • 1
    Can't you just let the object, which sets the condition to true, call a method of your other object, which should do something when the condition is true? – F. Böller Aug 15 '14 at 11:18
  • @Tichodroma In my chase `condition()` is a function that returns `true`, if a website is rendered and shows a specific element. – user3561614 Aug 15 '14 at 11:20
  • It would make more sense checking inside the loop. Like sleep for 1000ms then check then sleep another 1000 etc. – gkrls Aug 15 '14 at 11:20
  • 3
    Your question is unclear. If your application is single-threaded (hence the `condition()` method is running in the same thread), then your only option is to cut the `sleep` period from 1000ms to 1ms, and poll the `condition()` method after every 1ms. If your application is multi-threaded, then you should redesign it (probably get rid of the whole loop to begin with). – barak manos Aug 15 '14 at 11:21
  • what is the nature of "condition" ? – Adrian Aug 15 '14 at 11:29
  • @F.Böller, barak manos I have only one theard. – user3561614 Aug 15 '14 at 11:30
  • @xgeorgekx I would have `condition()` only one times. – user3561614 Aug 15 '14 at 11:31
  • @Adrian `condition()` is a function that returns boolean. – user3561614 Aug 15 '14 at 11:45
  • @user3561614 the nature of your condition() function is the key. Are you expecting some user input? What are you "really" waiting for? – Adrian Aug 15 '14 at 12:41
  • @Adrian I'm waiting for my browser, which is loading a website. – user3561614 Aug 15 '14 at 18:54

7 Answers7

49

Assuming you want what you asked for, as opposed to suggestions for redesigning your code, you should look at Awaitility.

For example, if you want to see if a file will be created within the next 10 seconds, you do something like:

await().atMost(10, SECONDS).until(() -> myFile.exists());

It's mainly aimed at testing, but does the specific requested trick of waiting for an arbitrary condition, specified by the caller, without explicit synchronization or sleep calls. If you don't want to use the library, just read the code to see the way it does things.

Which, in this case, comes down to a similar polling loop to the question, but with a Java 8 lambda passed in as an argument, instead of an inline condition.

Michael
  • 2,443
  • 1
  • 21
  • 21
soru
  • 5,464
  • 26
  • 30
15

Have you thought about some classes from java.util.concurrent - for example a BlockingQueue? You could use:

BlockingQueue<Boolean> conditionMet = new BlockingQueue<Boolean>;
conditionMet.poll(10,TimeUnit.SECONDS);

And then in the code that changes your condition do this:

conditionMet.put(true);

EDIT:

Another example form java.util.concurrent may be CountDownLatch:

CountDownLatch siteWasRenderedLatch = new CountDownLatch(1);
boolean siteWasRendered = siteWasRenderedLatch.await(10,TimeUnit.SECONDS);

This way you'll wait 10 seconds or until the latch reaches zero. To reach zero all you have to do is:

siteWasRenderedLatch.countDown();

This way you won't need to use locks which would be needed in Condition examples presented by @Adrian. I think it's just simpler and straight-forward.

And if you don't like the naming 'Latch' or 'Queue' you can always wrap it into your own class called i.e. LimitedTimeCondition:

public class LimitedTimeCondition
{
    private CountDownLatch conditionMetLatch;
    private Integer unitsCount;
    private TimeUnit unit;

    public LimitedTimeCondition(final Integer unitsCount, final TimeUnit unit)
    {
        conditionMetLatch = new CountDownLatch(1);
        this.unitsCount = unitsCount;
        this.unit = unit;
    }

    public boolean waitForConditionToBeMet()
    {
        try
        {
            return conditionMetLatch.await(unitsCount, unit);
        }
        catch (final InterruptedException e)
        {
            System.out.println("Someone has disturbed the condition awaiter.");
            return false;
        }

    }

    public void conditionWasMet()
    {
        conditionMetLatch.countDown();
    }
}

And the usage would be:

LimitedTimeCondition siteRenderedCondition = new LimitedTimeCondition(10, TimeUnit.SECONDS);
//
...
//
if (siteRenderedCondition.waitForConditionToBeMet())
{
   doStuff();
}
else
{
   System.out.println("Site was not rendered properly");
}
//
...
// in condition checker/achiever:
if (siteWasRendered)
{
   condition.conditionWasMet();
}
Michał Schielmann
  • 1,372
  • 8
  • 17
  • Don't think this is likely to work; what happens when conditionMet.put(false) is called? – soru Aug 15 '14 at 22:30
  • Instead of setting the condition variable to true (and then returing it in the method 'condition()') you can put something in the queue. It may be boolean(no matter if true or false), Integer, String - whatever you want. If the condition is not met then don't put anything. We don't know anything about the condition here, so this is one of the proposals that can be used from java.util.concurrent. – Michał Schielmann Aug 16 '14 at 00:35
  • I've added another example that may be more proper/intuitive for this question. – Michał Schielmann Aug 16 '14 at 00:47
  • 1
    If you read the question, you see he is waiting on an external process. Your code would be polling in thread A, and then using this stuff to pass the result to thread B, which is blocked. This doens't solve the problem - it only shows how to communicate between A and B, not how to write A. And if you knew how to write A, you wouldn't need B. – soru Aug 16 '14 at 08:26
  • Also, the third example is not only 20 times longer than the code he wanted shortended, I think it leaves the superflous thread running on timeout. – soru Aug 16 '14 at 08:31
  • Im sorry, maybe I dont get something. In the question there was nothing about external process mentioned. As far as i can tell it can be another apps thread working to render the site. Event if it's external process it can have some listener like onSiteRendered associated. So I assume that there are 2 threads. 1 is rendering the site, and the 2nd is waiting for it to finish for a specific time. As soon as the site is rendered, the awaiting thread should be notified. And as I wrote earlier the third example is just for naming purposes. Usage of the class is shorter than the loop from question. – Michał Schielmann Aug 16 '14 at 09:57
  • CountDownLatch was an excellent suggestion—just what I needed for my scenario. – CarLuva Apr 06 '23 at 20:41
12

I didn't find a solution in the JDK. I think this feature should be added to the JDK.

Here what I've implemented with a Functional Interface:

import java.util.concurrent.TimeoutException;
import java.util.function.BooleanSupplier;

public interface WaitUntilUtils {

  static void waitUntil(BooleanSupplier condition, long timeoutms) throws TimeoutException{
    long start = System.currentTimeMillis();
    while (!condition.getAsBoolean()){
      if (System.currentTimeMillis() - start > timeoutms ){
        throw new TimeoutException(String.format("Condition not met within %s ms",timeoutms));
      }
    }
  }
}
Community
  • 1
  • 1
Tama
  • 449
  • 7
  • 12
  • 1
    If the system wall clock is changed while you're waiting on the condition, you might get a deadlock (or an early wait) if the condition is never met. You should use a monotonic clock for these kind of situations. – Fernando Silveira Oct 07 '21 at 08:52
  • 1
    Nice point thank you. Sommer/Winter time could be as well a problem i guess. i used this mainly for tests. TIL something very useful! – Tama Nov 04 '21 at 18:40
6

Have a look at Condition.

Conditions (also known as condition queues or condition variables) provide a means for one thread to suspend execution (to "wait") until notified by another thread that some state condition may now be true. Because access to this shared state information occurs in different threads, it must be protected, so a lock of some form is associated with the condition. The key property that waiting for a condition provides is that it atomically releases the associated lock and suspends the current thread, just like Object.wait.

A Condition instance is intrinsically bound to a lock. To obtain a Condition instance for a particular Lock instance use its newCondition() method.

EDIT:

Community
  • 1
  • 1
Adrian
  • 6,013
  • 10
  • 47
  • 68
3

You may want to use something like the code below (where secondsToWait holds the maximum number of seconds you want to wait to see if the condition() turns true. The varialbe isCondetionMet will contain true if the condition was found, or false if the code timed out waiting for the condition.

        long endWaitTime = System.currentTimeMillis() + secondsToWait*1000;
        boolean isConditionMet = false;
        while (System.currentTimeMillis() < endWaitTime && !isConditionMet) {
            isConditionMet = condition();
            if (isConditionMet) {
                break;
            } else {
                Thread.sleep(1000);
            }
        }
ErstwhileIII
  • 4,829
  • 2
  • 23
  • 37
  • This is a fair bit longer than the original code, for no obvious benefit. – soru Aug 15 '14 at 22:32
  • 1
    This code provides several advantages: it retains information about how the loop exited; it more clearly shows the time this code will "pause" ... particularly useful if in a single thread environment. – ErstwhileIII Aug 16 '14 at 22:29
1

Using await Awaitility:

Awaitility.with().pollDelay(1000, TimeUnit.MILLISECONDS).await().until(() -> true);
rec
  • 10,340
  • 3
  • 29
  • 43
0

I'm using the following adaptation of the original question's solution:

public class Satisfied {
    public static boolean inTime(Callable<Boolean> condition, int timeoutInSecs) {
        int count;
        try {
            for (count = 1; count < timeoutInSecs * 20 && !condition.call(); count++)
                Thread.sleep(50);
            return (count < timeoutInSecs * 20);
        } catch (Exception e) {
            throw new AssertionError(e.getMessage());
        }
    }
}

When used in testing, it appears like this:

assertThat(Satisfied.inTime(() -> myCondition(), 5)).isTrue();
Franco
  • 669
  • 2
  • 8
  • 23