web-dev-qa-db-ja.com

Future <T>のいずれかが完了するまで待ちます

実行中の非同期タスクはほとんどなく、そのうちの少なくとも1つが完了するまで待つ必要があります(将来的には、N個のタスクのうちutil Mが完了するまで待つ必要があります)。現在、それらはFutureとして表示されているため、次のようなものが必要です

/**
 * Blocks current thread until one of specified futures is done and returns it. 
 */
public static <T> Future<T> waitForAny(Collection<Future<T>> futures) 
        throws AllFuturesFailedException

このようなものはありますか?または、Futureに必要ではない類似の何か。現在、先物のコレクションをループし、1つが終了したかどうかを確認し、しばらく寝てから再度確認します。これは最善の解決策ではないように見えます。長時間スリープすると不要な遅延が追加され、短時間スリープするとパフォーマンスに影響する可能性があるためです。

使ってみた

new CountDownLatch(1)

タスクが完了したらカウントダウンを減らして

countdown.await()

、しかし、私は未来の創造をコントロールする場合にのみ可能だとわかった。可能ですが、システムの再設計が必要です。これは、現在、タスク作成のロジック(ExecutorServiceにCallableを送信)が、どのFutureを待つ決定から分離されているためです。オーバーライドすることもできます

<T> RunnableFuture<T> AbstractExecutorService.newTaskFor(Callable<T> callable)

runnableFutureのカスタム実装を作成し、タスクが終了したときに通知されるようリスナーをアタッチし、そのようなリスナーを必要なタスクにアタッチしてCountDownLatchを使用しますが、使用するすべてのExecutorServiceに対してnewTaskForをオーバーライドする必要があります-そして、実装がある可能性がありますAbstractExecutorServiceを拡張しません。特定のExecutorServiceを同じ目的でラッピングすることもできますが、Futureを生成するすべてのメソッドを装飾する必要があります。

これらのソリューションはすべて動作する可能性がありますが、非常に不自然に見えます。次のようなシンプルなものが欠けているようです

WaitHandle.WaitAny(WaitHandle[] waitHandles)

c#で。この種の問題の解決策はありますか?

更新:

もともと私は未来の創造物にまったくアクセスできなかったので、エレガントな解決策はありませんでした。システムを再設計した後、Future作成にアクセスし、countDownLatch.countdown()を実行プロセスに追加できました。その後、countDownLatch.await()を実行でき、すべてが正常に機能します。他の回答のおかげで、ExecutorCompletionServiceについては知りませんでしたが、実際には同様のタスクで役立ちますが、この特定のケースでは、いくつかのフューチャーがエグゼキューターなしで作成されるため、使用できませんでした-実際のタスクはネットワーク経由で別のサーバーに送信されますリモートで完了し、完了通知が受信されます。

54
Pavel Feldman

私の知る限り、Javaには_WaitHandle.WaitAny_メソッドに類似した構造はありません。

これは、「WaitableFuture」デコレータを使用して実現できるようです。

_public WaitableFuture<T>
    extends Future<T>
{
    private CountDownLatch countDownLatch;

    WaitableFuture(CountDownLatch countDownLatch)
    {
        super();

        this.countDownLatch = countDownLatch;
    }

    void doTask()
    {
        super.doTask();

        this.countDownLatch.countDown();
    }
}
_

ただし、これは実行コードの前に挿入できる場合にのみ機能します。それ以外の場合、実行コードには新しいdoTask()メソッドがありません。しかし、実行前に何らかの方法でFutureオブジェクトの制御を取得できない場合、ポーリングなしでこれを実行する方法はありません。

または、将来が常に独自のスレッドで実行され、何らかの方法でそのスレッドを取得できる場合。次に、新しいスレッドを生成して、他のスレッドを結合し、結合が戻った後に待機メカニズムを処理します。これは非常にく、多くのオーバーヘッドを引き起こします。また、一部のFutureオブジェクトが終了しない場合、デッドスレッドに応じてブロックされたスレッドが多くなる可能性があります。注意しないと、メモリとシステムリソースがリークする可能性があります。

_/**
 * Extremely ugly way of implementing WaitHandle.WaitAny for Thread.Join().
 */
public static joinAny(Collection<Thread> threads, int numberToWaitFor)
{
    CountDownLatch countDownLatch = new CountDownLatch(numberToWaitFor);

    foreach(Thread thread in threads)
    {
        (new Thread(new JoinThreadHelper(thread, countDownLatch))).start();
    }

    countDownLatch.await();
}

class JoinThreadHelper
    implements Runnable
{
    Thread thread;
    CountDownLatch countDownLatch;

    JoinThreadHelper(Thread thread, CountDownLatch countDownLatch)
    {
        this.thread = thread;
        this.countDownLatch = countDownLatch;
    }

    void run()
    {
        this.thread.join();
        this.countDownLatch.countDown();
    }
}
_
4
jdmichal

シンプル、チェックアウト ExecutorCompletionService

55
james
9
ykaganovich

なぜ結果キューを作成してキューで待機しないのですか?または、より単純に、CompletionServiceを使用します。これは、ExecutorService +結果キューであるためです。

7
Alex Miller

これは、wait()およびnotifyAll()を使用すると実際に非常に簡単です。

最初に、ロックオブジェクトを定義します。 (これには任意のクラスを使用できますが、明示的にしたいです):

package com.javadude.sample;

public class Lock {}

次に、ワーカースレッドを定義します。彼は、処理が終了したら、そのロックオブジェクトに通知する必要があります。通知は、ロックオブジェクトの同期ブロックロック内にある必要があることに注意してください。

package com.javadude.sample;

public class Worker extends Thread {
    private Lock lock_;
    private long timeToSleep_;
    private String name_;
    public Worker(Lock lock, String name, long timeToSleep) {
        lock_ = lock;
        timeToSleep_ = timeToSleep;
        name_ = name;
    }
    @Override
    public void run() {
        // do real work -- using a sleep here to simulate work
        try {
            sleep(timeToSleep_);
        } catch (InterruptedException e) {
            interrupt();
        }
        System.out.println(name_ + " is done... notifying");
        // notify whoever is waiting, in this case, the client
        synchronized (lock_) {
            lock_.notify();
        }
    }
}

最後に、クライアントを作成できます。

package com.javadude.sample;

public class Client {
    public static void main(String[] args) {
        Lock lock = new Lock();
        Worker worker1 = new Worker(lock, "worker1", 15000);
        Worker worker2 = new Worker(lock, "worker2", 10000);
        Worker worker3 = new Worker(lock, "worker3", 5000);
        Worker worker4 = new Worker(lock, "worker4", 20000);

        boolean started = false;
        int numNotifies = 0;
        while (true) {
            synchronized (lock) {
                try {
                    if (!started) {
                        // need to do the start here so we grab the lock, just
                        //   in case one of the threads is fast -- if we had done the
                        //   starts outside the synchronized block, a fast thread could
                        //   get to its notification *before* the client is waiting for it
                        worker1.start();
                        worker2.start();
                        worker3.start();
                        worker4.start();
                        started = true;
                    }
                    lock.wait();
                } catch (InterruptedException e) {
                    break;
                }
                numNotifies++;
                if (numNotifies == 4) {
                    break;
                }
                System.out.println("Notified!");
            }
        }
        System.out.println("Everyone has notified me... I'm done");
    }
}
6

どれが終了するかは気にしないので、すべてのスレッドに対して単一のWaitHandleを用意して、待機するだけではどうでしょうか。どちらを先に終了しても、ハンドルを設定できます。

0