5

2 つのタスクがあります。最初のタスク ( work) は繰り返し発生し、2 番目のタスク ( cleanup) は一部のリソースを解放します。タスクは、繰り返し発生するタスクが完了しcleanupた後に 1 回だけ実行する必要があり、work再度実行されることはありません。

私の最初の本能は次のようなものでした:

ScheduledExecutorService service = ...;
ScheduledFuture<?> future = service.scheduleAtFixedRate(work, ...);

// other stuff happens

future.cancel(false);
cleanup.run();

ここでの問題は、cancel()すぐに戻ることです。したがって、workたまたま実行されている場合は、それcleanupが重複します。

理想的には、Guava's のようなものを使用しますFutures.addCallback(ListenableFuture future, FutureCallback callback)。(Guava 15にはそのようなものがあるかもしれません)。

futureそれまでの間、がキャンセルされて実行さ workなくなったときに、どのようにコールバックを起動できますか?

4

3 に答える 3

1

これが私が思いついた解決策です。それはかなり単純に思えますが、もっと一般的でエレガントな解決策があると思います。グアバのような図書館で見たいです...

まず、ランナブルに相互排除を適用するラッパーを作成します。

private static final class SynchronizedRunnable implements Runnable {
    private final Object monitor;
    private final Runnable delegate;

    private SynchronizedRunnable(Object monitor, Runnable delegate) {
        this.monitor = monitor;
        this.delegate = delegate;
    }

    @Override
    public void run() {
        synchronized (monitor) {
            delegate.run();
        }
    }
}

次に、呼び出しが成功したときにコールバックを起動するラッパーを作成しますcancel

private static final class FutureWithCancelCallback<V> extends ForwardingFuture.SimpleForwardingFuture<V> {

    private final Runnable callback;

    private FutureWithCancelCallback(Future<V> delegate, Runnable callback) {
        super(delegate);
        this.callback = callback;
    }

    @Override
    public boolean cancel(boolean mayInterruptIfRunning) {
            boolean cancelled = super.cancel(mayInterruptIfRunning);
            if (cancelled) {
                callback.run();
            }
            return cancelled;
    }
}

次に、独自の方法ですべてをまとめます。

private Future<?> scheduleWithFixedDelayAndCallback(ScheduledExecutorService service, Runnable work, long initialDelay, long delay, TimeUnit unit, Runnable cleanup) {

    Object monitor = new Object();

    Runnable monitoredWork = new SynchronizedRunnable(monitor, work);

    Runnable monitoredCleanup = new SynchronizedRunnable(monitor, cleanup);

    Future<?> rawFuture = service.scheduleAtFixedRate(monitoredWork, initialDelay, delay, unit);

    Future<?> wrappedFuture = new FutureWithCancelCallback(rawFuture, monitoredCleanup);

    return wrappedFuture;
}
于 2013-04-03T18:30:34.857 に答える
1

じゃあもう一押しします。コマンドを拡張するか、実行したRunnable/をラップすることができますCallable。これを見てください:

public static class RunnableWrapper implements Runnable {

    private final Runnable original;
    private final Lock lock = new ReentrantLock();

    public RunnableWrapper(Runnable original) {
        this.original = original;
    }

    public void run() {
        lock.lock();
        try {
            this.original.run();
        } finally {
            lock.unlock();
        }
    }

    public void awaitTermination() {
        lock.lock();
        try {
        } finally {
            lock.unlock();
        }
    }

}

したがって、コードを次のように変更できます

ScheduledExecutorService service = ...;
RunnableWrapper wrapper = new RunnableWrapper(work);
ScheduledFuture<?> future = service.scheduleAtFixedRate(wrapper, ...);

// other stuff happens

future.cancel(false);
wrapper.awaitTermination();
cleanup.run();

を呼び出した後、実行されていなくてすぐに戻るか、実行さcancelれていて完了するまでブロックされます。workawaitTermination()awaitTermination()

于 2013-04-04T09:40:55.403 に答える
0

やってみませんか

// other stuff happens

future.cancel(false);
service.shutdown();
service.awaitTermination(1, TimeUnit.DAYS);
cleanup.run();

これにより、エグゼキューター サービスがシャットダウンするように指示されるため、実行中の可能性があるサービスが終了するのを待つことができますwork

于 2013-04-03T20:49:18.260 に答える