Добавить / расширить поведение будущего, созданного ListeningExecutorService

конечной целью является добавление дополнительного поведения в ListenableFutures на основе типа Callable/Runnable аргумент. Я хочу добавить дополнительное поведение к каждому из будущее методы. (Примеры использования можно найти в javadoc AbstractExecutorService и раздел 7.1.7 Гетца параллелизм Java на практике)

у меня есть ExecutorService, который переопределяет newTaskFor. Он проверяет тип аргумента и создает подкласс FutureTask. Это, естественно, поддерживает submit, а также invokeAny и invokeAll.

как получить тот же эффект для ListenableFuture s возвращается ListeningExecutorService?

иными словами, где я могу поместить этот код

if (callable instanceof SomeClass) {
   return new FutureTask<T>(callable) {
        public boolean cancel(boolean mayInterruptIfRunning) {
            System.out.println("Canceling Task");
            return super.cancel(mayInterruptIfRunning);
        }
    };
} else {
    return new FutureTask<T>(callable);
}

такой, что мой клиент может оформить println заявление с

ListeningExecutorService executor = ...;
Collection<Callable> callables = ImmutableSet.of(new SomeClass());
List<Future<?>> futures = executor.invokeAll(callables);
for (Future<?> future : futures) {
    future.cancel(true);
}

Неудачных Решений

вот список вещей, которые я уже пробовал и почему они не работают.

Решение A

передать MyExecutorService to MoreExecutors.listeningDecorator.

Проблема 1: к сожалению, в результате ListeningExecutorService (an AbstractListeningExecutorService) не делегировать ExecutorService методы, делегаты execute (Runnable) метод on исполнитель. В результате newTaskFor метод on MyExecutorService никогда не вызывается.

Проблема 2: AbstractListeningExecutorService создает Runnable (a ListenableFutureTask) через статический заводской метод, который я не могу расширить.

Решение B

внутри newTaskFor, create MyRunnableFuture нормально, а затем оберните его с ListenableFutureTask.

Проблема 1: ListenableFutureTaskзаводские методы не принимают результат того,s, они принимают Runnable и Callable. Если я пройду MyRunnableFuture как Runnable, в результате ListenableFutureTask просто называет run() и Future методы (где мое поведение).

Проблема 2: даже если он назвал мой Future методы MyRunnableFuture - это не Callable, поэтому я должен предоставить возвращаемое значение при создании ListenableFutureTask... которого у меня нет... следовательно the Callable.

Решение C

пусть MyRunnableFuture продлить ListenableFutureTask вместо FutureTask

: ListenableFutureTask теперь окончательно (по состоянию на r10 / r11).

Решение D

пусть MyRunnableFuture расширения ForwardingListenableFuture и реализовать результат того,. Затем оберните в ListenableFutureTask и вернуть, что из delegate()

: он зависает. Я не понимаю проблему достаточно хорошо, чтобы объяснить ее, но эта конфигурация вызывает тупик в FutureTask.Синхронизация.

Исходный Код: по запросу, вот источник решения D, который зависает:

import java.util.*;
import java.util.concurrent.*;
import com.google.common.collect.ImmutableSet;
import com.google.common.util.concurrent.*;

/** See http://stackoverflow.com/q/8931215/290943 */
public final class MyListeningExecutorServiceD extends ThreadPoolExecutor implements ListeningExecutorService {

    // ===== Test Harness =====

    private static interface SomeInterface {
        public String getName();
    }

    private static class SomeClass implements SomeInterface, Callable<Void>, Runnable {
        private final String name;

        private SomeClass(String name) {
            this.name = name;
        }

        public Void call() throws Exception {
            System.out.println("SomeClass.call");
            return null;
        }

        public void run() {
            System.out.println("SomeClass.run");
        }

        public String getName() {
            return name;
        }
    }

    private static class MyListener implements FutureCallback<Void> {
        public void onSuccess(Void result) {
            System.out.println("MyListener.onSuccess");
        }

        public void onFailure(Throwable t) {
            System.out.println("MyListener.onFailure");
        }
    }

    public static void main(String[] args) throws InterruptedException {
        System.out.println("Main.start");

        SomeClass someClass = new SomeClass("Main.someClass");

        ListeningExecutorService executor = new MyListeningExecutorServiceD();
        Collection<Callable<Void>> callables = ImmutableSet.<Callable<Void>>of(someClass);
        List<Future<Void>> futures = executor.invokeAll(callables);

        for (Future<Void> future : futures) {
            Futures.addCallback((ListenableFuture<Void>) future, new MyListener());
            future.cancel(true);
        }

        System.out.println("Main.done");
    }

    // ===== Implementation =====

    private static class MyRunnableFutureD<T> extends ForwardingListenableFuture<T> implements RunnableFuture<T> {

        private final ListenableFuture<T> delegate;
        private final SomeInterface someClass;

        private MyRunnableFutureD(SomeInterface someClass, Runnable runnable, T value) {
            assert someClass == runnable;
            this.delegate = ListenableFutureTask.create(runnable, value);
            this.someClass = someClass;
        }

        private MyRunnableFutureD(SomeClass someClass, Callable<T> callable) {
            assert someClass == callable;
            this.delegate = ListenableFutureTask.create(callable);
            this.someClass = someClass;
        }

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

        public void run() {
            System.out.println("MyRunnableFuture.run");
            try {
                delegate.get();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }

        @Override
        public boolean cancel(boolean mayInterruptIfRunning) {
            System.out.println("MyRunnableFuture.cancel " + someClass.getName());
            return super.cancel(mayInterruptIfRunning);
        }
    }

    public MyListeningExecutorServiceD() {
        // Same as Executors.newSingleThreadExecutor for now
        super(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
    }

    @Override
    protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
        if (runnable instanceof SomeClass) {
            return new MyRunnableFutureD<T>((SomeClass) runnable, runnable, value);
        } else {
            return new FutureTask<T>(runnable, value);
        }
    }

    @Override
    protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
        if (callable instanceof SomeClass) {
            return new MyRunnableFutureD<T>((SomeClass) callable, callable);
        } else {
            return new FutureTask<T>(callable);
        }
    }

    /** Must override to supply co-variant return type */
    @Override
    public ListenableFuture<?> submit(Runnable task) {
        return (ListenableFuture<?>) super.submit(task);
    }

    /** Must override to supply co-variant return type */
    @Override
    public <T> ListenableFuture<T> submit(Runnable task, T result) {
        return (ListenableFuture<T>) super.submit(task, result);
    }

    /** Must override to supply co-variant return type */
    @Override
    public <T> ListenableFuture<T> submit(Callable<T> task) {
        return (ListenableFuture<T>) super.submit(task);
    }
}

5 ответов


основываясь на этом вопросе и нескольких других обсуждениях, которые у меня были недавно, я прихожу к выводу, что RunnableFuture/FutureTask по своей сути вводит в заблуждение: ясно, что вы представляете Runnable, и ясно, что вы получить Future назад, и явно лежащий в основе Thread нужен Runnable. Но почему класс должен реализовывать оба Runnable и Future? И если это произойдет, то что?--3--> - это его замена? Это уже достаточно плохо, но затем мы вводим несколько уровней исполнителей, и все действительно становится из руки.

если здесь есть решение, я думаю, что это требует лечения FutureTask как деталь реализации AbstractExecutorService. Вместо этого я бы сосредоточился на разделении проблемы на две части:

  • я хочу условно изменить возвращаемый Future.
  • я хочу условно изменить код, выполняемый службой исполнителя. (На самом деле я не уверен, является ли это требование, но я прикрою ее в случае. Даже если нет, это может помогите установить Runnable/Future различие.)

(ворчание уценки ворчание)

class MyWrapperExecutor extends ForwardingListeningExecutorService {
  private final ExecutorService delegateExecutor;

  @Override public <T> ListenableFuture<T> submit(Callable<T> task) {
    if (callable instanceof SomeClass) {
      // Modify and submit Callable (or just submit the original Callable):
      ListenableFuture<T> delegateFuture =
          delegateExecutor.submit(new MyCallable(callable));
      // Modify Future:
      return new MyWrapperFuture<T>(delegateFuture);
    } else {
      return delegateExecutor.submit(callable);
    }
  }

  // etc.
}

может ли это сработать?


согласно ListeningExecutorService Javadoc, Вы можете использовать MoreExecutors.listeningDecorator, чтобы украсить свой собственный ExecutorService.

поэтому используйте ExecutorService, который переопределяет newTaskFor, и оберните его указанным выше методом. Это сработает для тебя?

обновление

ОК, Вот что я бы сделал:

1) Загрузите источники Guava, если вы еще этого не сделали.

2) Не используйте listeningDecorator, вместо этого сделайте свой пользовательский ExecutorService реализовать ListeningExecutorService.

3) Ваш подкласс FutureTask должен реализовать ListenableFuture и скопировать код из ListenableFutureTask, что довольно просто, а затем добавить переопределение метода cancel.

4) реализуйте методы ListeningExecutorService в пользовательском ExecutorService, изменив метод возврата существующих методов на ListenableFuture.


Я подозреваю, что MoreExecutors.listeningDecorator(service) изменит фьючерсы, возвращенные вашим базовым service. Поэтому, если вы уже изменили свой базовый ExecutorService, и вы просто украшаете Future методы, вы можете просто использовать MoreExecutors.listeningDecorator(service) напрямую. Вы экспериментировали, чтобы убедиться, что это работает? Если нет, можете ли вы предоставить более подробную информацию о том, почему это не сработает?

----обновление----

похоже, что ваш код, как написано, смешивает submit и invokeAll. (В частности, вызывает invokeAll,который не делегирует для отправки...)

что сказал, выводу, я быстро рисунок ListenableFutures и ListeningExecutorService не должны злоупотреблять этим способом. Реализация этого материала без тупиков-настоящая трудная проблема, и вы не должны этого делать, если можете этого избежать.

Я подозреваю, что, возможно, стоит подать вопрос гуавы, что решение A не работает,может быть. но я продолжаю пытаться выяснить, как делать то, что ты делаешь, и это кажется неправильным.

можете ли вы дать некоторое представление как почему вы пытаетесь вернуть разные фьючерсы?


javadoc для MoreExecutors.listeningDecorator ясно заявляет, что он делегирует execute и никогда не вызывает делегата submit, invokeAll и invokeAny. Кроме того, в нем говорится, что любая "специальная обработка задач должна быть реализована в делегате execute метод или путем обертывания возвращенного ListeningExecutorService."

используя newTaskFor выходит. Период.

я могу получить большую часть поведения, которое я хочу, делая (источник включен ниже):

  1. переместить поведение, которое я хочу добавить в подкласс ForwardingListenableFuture (MyListenableFuture)
  2. пусть MyExecutorService расширения ForwardingListeningExecutorService
  3. использовать MoreExecutors.listeningDecorator обернуть стандарт ExecutorService без переопределенный newTaskFor метод.
  4. переопределить submit для обертывания ListenableFuture, возвращаемого listeningDecorator с помощью MyListenableFuture

единственный "пробел", который у меня остался, это invokeAll. Есть два проблемы:

  1. (минор) если я предполагаю Future s возвращается listeningDecorator "в том же порядке" как Callables, что я подал на него, то я могу пройти по списку Callables и Futures создание MyListenableFuture для каждой пары. Это, вероятно, безопасное предположение, но все же не совсем верное. (Мне также придется скопировать CallableS из моего аргумента, чтобы избежать перемежающейся мутации, но это нормально)
  2. (больше) AbstractListeningExecutorService вызывает cancel, isDone, и get внутри invokeAll. Это означает, что я не могу добавить свое поведение к этим методам до их вызова.

источник

import java.util.concurrent.*;
import com.google.common.util.concurrent.*;

/** See http://stackoverflow.com/q/8931215/290943 */
public final class MyListeningExecutorServiceE extends ForwardingListeningExecutorService {

    // ===== Test Harness =====

    private static interface SomeInterface {
        public String getName();
    }

    private static class SomeClass implements SomeInterface, Callable<Void>, Runnable {
        private final String name;

        private SomeClass(String name) {
            this.name = name;
        }

        public Void call() throws Exception {
            System.out.println("SomeClass.call");
            return null;
        }

        public void run() {
            System.out.println("SomeClass.run");
        }

        public String getName() {
            return name;
        }
    }

    private static class MyListener implements FutureCallback<Void> {
        public void onSuccess(Void result) {
            System.out.println("MyListener.onSuccess");
        }

        public void onFailure(Throwable t) {
            System.out.println("MyListener.onFailure");
        }
    }

    public static void main(String[] args) throws InterruptedException {
        System.out.println("Main.start");

        SomeInterface someClass = new SomeClass("Main.someClass");

        ListeningExecutorService executor = new MyListeningExecutorServiceE();

        ListenableFuture<Void> future = executor.submit((Callable<Void>) someClass);
        Futures.addCallback(future, new MyListener());
        future.cancel(true);

        /*  Not supported by this implementation

        Collection<Callable<Void>> callables = ImmutableSet.<Callable<Void>>of(someClass);
        List<Future<Void>> futures = executor.invokeAll(callables);

        for (Future<Void> future : futures) {
            Futures.addCallback((ListenableFuture<Void>) future, new MyListener());
            future.cancel(true);
        }
        */

        executor.shutdown();
        System.out.println("Main.done");
    }

    // ===== Implementation =====

    private static class MyListenableFutureE<T> extends ForwardingListenableFuture<T> {

        private final ListenableFuture<T> delegate;
        private final SomeInterface someInterface;

        private MyListenableFutureE(SomeInterface someInterface, ListenableFuture<T> delegate) {
            this.delegate = delegate;
            this.someInterface = someInterface;
        }

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

        @Override
        public boolean cancel(boolean mayInterruptIfRunning) {
            System.out.println("MyRunnableFuture.cancel " + someInterface.getName());
            return super.cancel(mayInterruptIfRunning);
        }
    }

    private final ListeningExecutorService delegate;

    public MyListeningExecutorServiceE() {
        delegate = MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor());
    }

    @Override
    protected ListeningExecutorService delegate() {
        return delegate;
    }

    @Override
    public <T> ListenableFuture<T> submit(Callable<T> task) {
        ListenableFuture<T> future = super.submit(task);

        if (task instanceof SomeInterface) {
            future = new MyListenableFutureE<T>((SomeInterface) task, future);
        }

        return future;
    }

    @Override
    public ListenableFuture<?> submit(Runnable task) {
        ListenableFuture<?> future = super.submit(task);

        if (task instanceof SomeInterface) {
            future = new MyListenableFutureE((SomeInterface) task, future);
        }

        return future;
    }

    @Override
    public <T> ListenableFuture<T> submit(Runnable task, T result) {
        ListenableFuture<T> future = super.submit(task, result);

        if (task instanceof SomeInterface) {
            future = new MyListenableFutureE<T>((SomeInterface) task, future);
        }

        return future;
    }
}

хороший улов на invokeAll проблемы с моим предыдущим предложением.

может быть, я все еще слишком много думаю об этом. Может реализовать ListenableFuture и MyExecutorService реализует ListeningExecutorService? Вы все равно продлите AbstractExecutorService, но вы объявите, что вы реализуете ListeningExecutorService, так же. Потому что все методы AbstractExecutorService используйте объект, возвращаемый newTaskFor, и потому что вы знаете, что вы вернетесь ListenableFuture из этого метода, вы можете просто переопределить все их объявить возвращаемый тип ListenableFuture и реализовать их по линиям return (ListenableFuture<T>) super.foo().

если это сработает, возможно, мы должны сделать наш AbstractListeningExecutorService class public и добавить некоторые крючки, которые сделают это проще?