web-dev-qa-db-fra.com

Envelopper un calcul asynchrone dans un calcul synchrone (blocage)

questions similaires:

J'ai un objet avec une méthode que je voudrais exposer aux clients de bibliothèque (en particulier les clients de script) comme quelque chose comme:

interface MyNiceInterface
{
    public Baz doSomethingAndBlock(Foo fooArg, Bar barArg);
    public Future<Baz> doSomething(Foo fooArg, Bar barArg);
    // doSomethingAndBlock is the straightforward way;
    // doSomething has more control but deals with
    // a Future and that might be too much hassle for
    // scripting clients
}

mais le "truc" primitif dont je dispose est un ensemble de classes événementielles:

interface BazComputationSink
{
    public void onBazResult(Baz result);
}

class ImplementingThing
{
    public void doSomethingAsync(Foo fooArg, Bar barArg, BazComputationSink sink);
}

où ImplementingThing prend des entrées, fait des trucs mystérieux comme mettre en file d'attente des choses dans une file d'attente de tâches, puis plus tard lorsqu'un résultat se produit, sink.onBazResult() est appelé sur un thread qui peut être ou non le même thread que ImplementingThing.doSomethingAsync () a été appelé.

Existe-t-il un moyen d'utiliser les fonctions événementielles dont je dispose, ainsi que les primitives de concurrence, pour implémenter MyNiceInterface afin que les clients de script puissent joyeusement attendre sur un thread de blocage?

edit: puis-je utiliser FutureTask pour cela?

48
Jason S

Utilisation de votre propre implémentation Future:

public class BazComputationFuture implements Future<Baz>, BazComputationSink {

    private volatile Baz result = null;
    private volatile boolean cancelled = false;
    private final CountDownLatch countDownLatch;

    public BazComputationFuture() {
        countDownLatch = new CountDownLatch(1);
    }

    @Override
    public boolean cancel(final boolean mayInterruptIfRunning) {
        if (isDone()) {
            return false;
        } else {
            countDownLatch.countDown();
            cancelled = true;
            return !isDone();
        }
    }

    @Override
    public Baz get() throws InterruptedException, ExecutionException {
        countDownLatch.await();
        return result;
    }

    @Override
    public Baz get(final long timeout, final TimeUnit unit)
            throws InterruptedException, ExecutionException, TimeoutException {
        countDownLatch.await(timeout, unit);
        return result;
    }

    @Override
    public boolean isCancelled() {
        return cancelled;
    }

    @Override
    public boolean isDone() {
        return countDownLatch.getCount() == 0;
    }

    public void onBazResult(final Baz result) {
        this.result = result;
        countDownLatch.countDown();
    }

}

public Future<Baz> doSomething(Foo fooArg, Bar barArg) {
    BazComputationFuture future = new BazComputationFuture();
    doSomethingAsync(fooArg, barArg, future);
    return future;
}

public Baz doSomethingAndBlock(Foo fooArg, Bar barArg) {
    return doSomething(fooArg, barArg).get();
}

La solution crée un CountDownLatch en interne qui est effacé une fois le rappel reçu. Si les appels utilisateur sont récupérés, le CountDownLatch est utilisé pour bloquer le thread appelant jusqu'à la fin du calcul et appeler le rappel onBazResult. Le CountDownLatch garantira que si le rappel se produit avant l'appel de get (), la méthode get () retournera immédiatement avec un résultat.

46
Michael Barker

Eh bien, il y a la solution simple de faire quelque chose comme:

public Baz doSomethingAndBlock(Foo fooArg, Bar barArg) {
  final AtomicReference<Baz> notifier = new AtomicReference();
  doSomethingAsync(fooArg, barArg, new BazComputationSink() {
    public void onBazResult(Baz result) {
      synchronized (notifier) {
        notifier.set(result);
        notifier.notify();
      }
    }
  });
  synchronized (notifier) {
    while (notifier.get() == null)
      notifier.wait();
  }
  return notifier.get();
}

Bien sûr, cela suppose que votre résultat Baz ne sera jamais nul…

17
Paul Wagland

La google bibliothèque de goyave a un SettableFuture facile à utiliser qui rend ce problème très simple (environ 10 lignes de code).

public class ImplementingThing {

public Baz doSomethingAndBlock(Foo fooArg, Bar barArg) {
    try {
        return doSomething(fooArg, barArg).get();
    } catch (Exception e) {
        throw new RuntimeException("Oh dear");
    }
};

public Future<Baz> doSomething(Foo fooArg, Bar barArg) {
    final SettableFuture<Baz> future = new SettableFuture<Baz>();
    doSomethingAsync(fooArg, barArg, new BazComputationSink() {
        @Override
        public void onBazResult(Baz result) {
            future.set(result);
        }
    });
    return future;
};

// Everything below here is just mock stuff to make the example work,
// so you can copy it into your IDE and see it run.

public static class Baz {}
public static class Foo {}
public static class Bar {}

public static interface BazComputationSink {
    public void onBazResult(Baz result);
}

public void doSomethingAsync(Foo fooArg, Bar barArg, final BazComputationSink sink) {
    new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                Thread.sleep(4000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Baz baz = new Baz();
            sink.onBazResult(baz);
        }
    }).start();
};

public static void main(String[] args) {
    System.err.println("Starting Main");
    System.err.println((new ImplementingThing()).doSomethingAndBlock(null, null));
    System.err.println("Ending Main");
}
12
kybernetikos

Un exemple très simple, juste pour comprendre CountDownLatch sans code supplémentaire.

Un Java.util.concurrent.CountDownLatch Est une construction simultanée qui permet à un ou plusieurs threads d'attendre la fin d'un ensemble d'opérations donné.

Un CountDownLatch est initialisé avec un nombre donné. Ce nombre est décrémenté par des appels à la méthode countDown(). Les threads attendant que ce nombre atteigne zéro peuvent appeler l'une des méthodes await(). L'appel de await() bloque le thread jusqu'à ce que le nombre atteigne zéro.

Voici un exemple simple. Après que le décrémenteur ait appelé countDown() 3 fois sur le CountDownLatch, le serveur en attente est libéré de l'appel await().

Vous pouvez également mentionner quelques TimeOut à attendre.

CountDownLatch latch = new CountDownLatch(3);

Waiter      waiter      = new Waiter(latch);
Decrementer decrementer = new Decrementer(latch);

new Thread(waiter)     .start();
new Thread(decrementer).start();

Thread.sleep(4000);
public class Waiter implements Runnable{

    CountDownLatch latch = null;

    public Waiter(CountDownLatch latch) {
        this.latch = latch;
    }

    public void run() {
        try {
            latch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("Waiter Released");
    }
}

// --------------

public class Decrementer implements Runnable {

    CountDownLatch latch = null;

    public Decrementer(CountDownLatch latch) {
        this.latch = latch;
    }

    public void run() {

        try {
            Thread.sleep(1000);
            this.latch.countDown();

            Thread.sleep(1000);
            this.latch.countDown();

            Thread.sleep(1000);
            this.latch.countDown();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

Référence

Si vous ne voulez pas utiliser un CountDownLatch ou votre exigence est quelque chose de similaire à Facebook et contrairement aux fonctionnalités. Signifie que si une méthode est appelée, n'appelez pas l'autre méthode.

Dans ce cas, vous pouvez déclarer un

private volatile Boolean isInprocessOfLikeOrUnLike = false;

et vous pouvez vérifier au début de votre appel de méthode que s'il s'agit de false, alors la méthode call sinon return .. dépend de votre implémentation.

4
AZ_

C'est très simple avec RxJava 2.x:

try {
    Baz baz = Single.create((SingleEmitter<Baz> emitter) ->
            doSomethingAsync(fooArg, barArg, result -> emitter.onSuccess(result)))
            .toFuture().get();
} catch (InterruptedException e) {
    e.printStackTrace();
} catch (ExecutionException e) {
    e.printStackTrace();
}

Ou sans notation Lambda:

Baz baz = Single.create(new SingleOnSubscribe<Baz>() {
                @Override
                public void subscribe(SingleEmitter<Baz> emitter) {
                    doSomethingAsync(fooArg, barArg, new BazComputationSink() {
                        @Override
                        public void onBazResult(Baz result) {
                            emitter.onSuccess(result);
                        }
                    });
                }
            }).toFuture().get();

Encore plus simple:

Baz baz = Single.create((SingleEmitter<Baz> emitter) ->
                doSomethingAsync(fooArg, barArg, result -> emitter.onSuccess(result)))
                .blockingGet();
4
Emanuel Moecklin

Voici une solution plus générique basée sur la réponse de Paul Wagland:

public abstract class AsyncRunnable<T> {
    protected abstract void run(AtomicReference<T> notifier);

    protected final void finish(AtomicReference<T> notifier, T result) {
        synchronized (notifier) {
            notifier.set(result);
            notifier.notify();
        }
    }

    public static <T> T wait(AsyncRunnable<T> runnable) {
        final AtomicReference<T> notifier = new AtomicReference<>();

        // run the asynchronous code
        runnable.run(notifier);

        // wait for the asynchronous code to finish
        synchronized (notifier) {
            while (notifier.get() == null) {
                try {
                    notifier.wait();
                } catch (InterruptedException ignore) {}
            }
        }

        // return the result of the asynchronous code
        return notifier.get();
    }
}

Voici un exemple d'utilisation:

    String result = AsyncRunnable.wait(new AsyncRunnable<String>() {
        @Override
        public void run(final AtomicReference<String> notifier) {
            // here goes your async code, e.g.:
            new Thread(new Runnable() {
                @Override
                public void run() {
                    finish(notifier, "This was a asynchronous call!");
                }
            }).start();
        }
    });

Une version plus détaillée du code peut être trouvée ici: http://Pastebin.com/hKHJUBqE

EDIT: L'exemple lié à la question serait:

public Baz doSomethingAndBlock(final Foo fooArg, final Bar barArg) {
    return AsyncRunnable.wait(new AsyncRunnable<Baz>() {
        @Override
        protected void run(final AtomicReference<Baz> notifier) {
            doSomethingAsync(fooArg, barArg, new BazComputationSink() {
                public void onBazResult(Baz result) {
                    synchronized (notifier) {
                        notifier.set(result);
                        notifier.notify();
                    }
                }
            });
        }
    });
}
3
Emanuel Moecklin

La façon la plus simple (qui fonctionne pour moi) est de

  1. Créer une file d'attente de blocage
  2. Appelez la méthode asynchrone - utilisez un gestionnaire qui offre le résultat à cette file d'attente de blocage.
  3. Interrogez la file d'attente (c'est là que vous bloquez) pour le résultat.

    public Baz doSomethingAndBlock(Foo fooArg, Bar barArg) throws InterruptedException {
        final BlockingQueue<Baz> blocker = new LinkedBlockingQueue();
        doSomethingAsync(fooArg, barArg, blocker::offer);
        // Now block until response or timeout
        return blocker.poll(30, TimeUnit.SECONDS);
    }
    
1
Joel Shemtov