web-dev-qa-db-ja.com

非同期計算を同期(ブロッキング)計算にラップする

同様の質問:

ライブラリクライアント(特にスクリプトクライアント)に公開したいメソッドを持つオブジェクトがあります。

_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
}
_

しかし、私が利用できる基本的な「もの」は、イベント駆動型クラスのセットです。

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

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

ここで、ImplementingThingは入力を受け取り、タスクキューにエンキューするようないくつかの難解なことを行います。その後、結果が発生したときに、sink.onBazResult()が、ImplementingThing.doSomethingAsyncと同じスレッドまたは異なるスレッドで呼び出されます() と呼ばれていました。

私が持っているイベント駆動型の関数と同時実行プリミティブを使用してMyNiceInterfaceを実装し、スクリプトクライアントがブロックスレッドで楽しく待機できる方法はありますか?

edit:FutureTask を使用できますか?

48
Jason S

独自の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();
}

ソリューションは、CountDownLatchを内部的に作成し、コールバックが受信されるとクリアされます。ユーザーがgetを呼び出す場合、CountDownLatchを使用して、計算が完了するまで呼び出しスレッドをブロックし、onBazResultコールバックを呼び出します。 CountDownLatchは、get()が呼び出される前にコールバックが発生した場合、get()メソッドがすぐに結果を返すことを保証します。

46
Michael Barker

さて、次のようなことを行う簡単な解決策があります:

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();
}

もちろん、これはBaz結果が決してnullにならないことを前提としています…

17
Paul Wagland

グーグル グアバライブラリ には、この問題を非常に単純にする使いやすいSettableFutureがあります(約10行のコード)。

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

CountDownLatchを理解するための、非常に単純な例です。追加のコードは必要ありません。

_Java.util.concurrent.CountDownLatch_は、1つまたは複数のスレッドが特定の一連の操作が完了するのを待機できるようにする同時実行構文です。

CountDownLatchは、指定されたカウントで初期化されます。このカウントは、countDown()メソッドの呼び出しによって減少します。このカウントがゼロに達するのを待っているスレッドは、await()メソッドの1つを呼び出すことができます。 await()を呼び出すと、カウントがゼロになるまでスレッドがブロックされます。

以下は簡単な例です。 DecrementerがCountDownLatchcountDown()を3回呼び出した後、待機中のウェイターがawait()呼び出しから解放されます。

待機するTimeOutについて言及することもできます。

_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();
        }
    }
}
_

参考

CountDownLatchを使用したくない場合、または要件がFacebookと同じで機能が異なる場合。 1つのメソッドが呼び出されている場合は、他のメソッドを呼び出さないことを意味します。

その場合は、

_private volatile Boolean isInprocessOfLikeOrUnLike = false;
_

次に、メソッド呼び出しの最初に、それがfalseの場合はメソッドの呼び出しを返すかどうかを確認できます。実装によって異なります。

4
AZ_

これは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();
}

またはラムダ表記なし:

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();

さらにシンプル:

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

以下は、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();
    }
}

これを使用する方法の例を次に示します。

    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();
        }
    });

コードのより詳細なバージョンはここにあります: http://Pastebin.com/hKHJUBqE

編集:質問に関連する例は次のようになります:

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

(私のために働く)最も簡単な方法は

  1. ブロッキングキューを作成する
  2. 非同期メソッドを呼び出す-そのブロッキングキューに結果を提供するハンドラーを使用します。
  3. 結果のキュー(ブロックする場所)をポーリングします。

    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