4

ListenableFuture私たちのアプリケーションには、次のように、ベースのAPIを実装する多くのサービスがあります。

public interface MyService {
    ListenableFuture<Thing> getMyThing();
    ListenableFuture<?> putMyThing(Thing thing);
}

私たちのドメインモデルはまったくスレッドセーフではないため、前述のサービスを除いて、ほとんどのコードをよく知られたシングルスレッドで実行しますExecutorFutureサービスが、生成するに追加されたリスナーがその上で呼び出されることを保証できれば、それは素晴らしいことだと思いますExecutor

もちろん、を呼び出すかListenableFuture.addListener、適切な引数を使用して、サービスのクライアントでこれを強制することはできますが、私が目指しているのは、クライアントコードの複雑さとエラーの可能性を正確に減らすことなので、リスナーが欲しいですこれらのメソッドが引数を渡さずに呼び出されたときに、よく知られている場所で発生する呼び出し。Futures.addCallbackFutures.transformExecutorExecutorExecutor

したがって、今のところ、私はサービスのメソッドを次のように実装しています。

class MyServiceImpl {
    private Executor executor; /* the "main" executor */

    public ListenableFuture<Thing> getMyThing() {
        ListenableFuture<Thing> future = ...; /* actual service call */

        return Futures.transform(future, Functions.<Thing>identity(), executor );
    }
}

まず、これも機能しますか?Guavaの情報源からはそう思われますが、何らかの確認ができてうれしいので、これを単体テストすることを考えるのに少し苦労しています。

さらに、「サービスは指定されたスレッド(デフォルト)でコールバックする」パターン全体の有用性/コストの比率について少し心配しています。誰かがこのようなことを経験したことがありますか?このアプローチに隠れた落とし穴はありますか?

4

3 に答える 3

2

ListenableFutureすべてのメソッドが次のようなラッパーを返すようにすることはできませんでした。

import com.google.common.util.concurrent.ForwardingListenableFuture;
import com.google.common.util.concurrent.ListenableFuture;

import javax.annotation.Nonnull;
import java.util.concurrent.Executor;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;

public class ListenableFutureWithFixedExecutor<V> extends ForwardingListenableFuture<V> {

    @Nonnull
    private final ListenableFuture<V> delegate;
    @Nonnull
    private final Executor executor;

    public ListenableFutureWithFixedExecutor(@Nonnull ListenableFuture<V> delegate, @Nonnull Executor executor) {
        this.delegate = checkNotNull(delegate);
        this.executor = checkNotNull(executor);
    }

    @Override
    protected ListenableFuture<V> delegate() {
        return delegate;
    }

    @Override
    public void addListener(Runnable listener, Executor executor) {
        checkArgument(this.executor.equals(executor), "Listeners can only be executed using %s", executor);
        super.addListener(listener, executor);    //To change body of overridden methods use File | Settings | File Templates.
    }

}

IllegalArgumentException別の可能性:クライアントが誤ったコールバックで呼び出したときにをスローする代わりに、警告をログに記録し、正しいエグゼキュータaddListener()で呼び出すことができます。super.addListener()

<V> void addCallback(FutureCallback<? super V> callback)固定エグゼキュータを使用して、指定されたコールバックをFutureに追加するメソッドを追加することもできます。これにより、Futureをより流暢に使用できるようになります:)

于 2012-11-29T14:55:31.767 に答える
1

提案されたidentity()ソリューションは、2つの例外を除いて機能するはずです。

Futures.transform証明:まず、入力がいつ行われたかを知るための唯一の合理的な方法は、への呼び出しによるものであることに注意してinput.addListener()ください。私たちは、addListener()それが与えられたエグゼキュータを尊重することを知っています。(私を信じていない場合:ListenableFutureTaskusesはExecutionList、2つの場所でのみリスナーを呼び出します:add()execute()。両方の場所で、指定されたエグゼキューターを使用します。)したがって、入力Futureが完了したときに実行されるタスクはすべて、指定されたエグゼキューターで実行されます。 。

ここでのキーフレーズは「入力Futureが完了したときに実行する」です。例外:

  • が完了した後に誰かがリスナーを追加するFutureと、それはを呼び出すスレッドで実行されますaddListener
  • 誰かがFuture(元のラッパーではなく)ラッパーをキャンセルすると、リスナーはを呼び出すスレッドで実行されますcancel

アプリケーションの構造によっては、これらの例外は問題ない場合がありますが、注意が必要です。それらが問題である場合は、ForwardingListenableFuture解決策を使用することをお勧めします(eneveuのようですが、明らかにそれほど厳密ではありません)。

于 2012-12-03T19:09:59.073 に答える
0

次のような落とし穴があると思います。リスナーはで実行されますsameThreadExecutor()。つまり、リスナーはエグゼキュータ上のすべての同じスレッドで実行され、おそらく同じエグゼキュータ内のスレッド間で分散することはありません。

あなたがやろうとしていることをするのに便利な方法があるとは思いません。

于 2012-11-29T14:33:43.027 に答える