51 votes

Envelopper un calcul asynchrone dans un calcul synchrone (blocage)

des questions similaires:

J'ai un objet avec une méthode que je voudrais exposer à la bibliothèque de clients (en particulier les scripts clients) 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 la primitive "trucs" dont je dispose est un ensemble de event-driven classes:

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

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

où ImplementingThing prend entrées, n'certains arcanes des trucs comme enqueueing choses sur une tâche de la file d'attente, et puis plus tard, lorsqu'un résultat se produit, sink.onBazResult() est appelée sur un thread qui peut ou peut ne pas être le même thread que ImplementingThing.doSomethingAsync() a été appelé.

Est il possible que je peux utiliser l'événement piloté par les fonctions que j'ai, le long de la simultanéité des primitives, à mettre en œuvre MyNiceInterface donc scripts clients peut être heureux d'attendre sur un blocage de fil?

edit: puis-je utiliser FutureTask pour cela?

48voto

Michael Barker Points 8234

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 s'assurera que si le rappel se produit avant l'appel à get (), la méthode get () retournera immédiatement avec un résultat.

17voto

Paul Wagland Points 10487

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

 public Baz doSomethingAndBlock(Foo fooArg, Bar barArg) {
  final AtomicReference<Baz> notifier;
  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…

14voto

kybernetikos Points 3127

La bibliothèque google guava 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");
}
 

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X