Обнаружение исключения тайм-аута в Java Future без вызова get () для него - PullRequest
1 голос
/ 09 июля 2019

Я создаю библиотеку, которая нуждается в некоторых операциях Bluetooth на Android.Я хочу вернуть экземпляр Future, чтобы тот, кто использует мою библиотеку, мог вызывать .get () для возвращенного будущего и обрабатывать ExecutionException, TimeoutException и InterruptedException.Тем не менее, я хочу определить время ожидания самостоятельно, потому что мне нужна некоторая логика очистки, такая как отключение от устройства и так далее.Как мне этого добиться?

Ответы [ 2 ]

1 голос
/ 09 июля 2019

Вы можете реализовать класс-оболочку около Future, который делегирует другой (тот, который возвращается, где бы вы ни получили ваш Future в данный момент).Что-то вроде:

final class DelegatingFuture<T> implements Future<T> {

    private final Future<T> delegate;

    DelegatingFuture(final Future<T> delegate) {
        this.delegate = Objects.requireNonNull(delegate);
    }

    // All other methods simply delegate to 'delegate'
    @Override
    public T get() 
            throws InterruptedException, ExecutionException {
        try {
            return this.delegate.get();
        } catch (final Exception ex) {
            // Handle cleanup...
            throw ex;
        }
    }

    // Something similar for get(long timeout, TimeUnit unit)
}

А потом просто return new DelegatingFuture<>(currentFuture);, куда бы вы их не раздавали.

0 голосов
/ 10 июля 2019

Время ожидания относится к вызывающему методу get с таймаутом и только для этого вызывающего.Тайм-аут нигде не означает отмены.Например, следующий код является допустимым использованием Future API:

ExecutorService es = Executors.newSingleThreadExecutor();

Future<String> f = es.submit(() -> {
    Thread.sleep(3000);
    return "hello";
});

for(;;) try {
    String s = f.get(500, TimeUnit.MILLISECONDS);
    System.out.println("got "+s);
    break;
}
catch(TimeoutException ex) {
    // perhaps, do some other work
    System.out.println("will wait something more");
}
catch (ExecutionException ex) {
    System.out.println("failed with "+ex);
    break;
}

es.shutdown();

Привязка очистки к методам, фактически предназначенным для запроса результата, не является полезным подходом.Тайм-аут, предоставляемый вызывающим абонентом этого метода, не относится к фактической операции.Нет даже гарантии, что результат будет запрошен до завершения операций или что он вообще будет запрошен.

Очистка должна происходить, когда либо операция завершена, либо когда будущее явно отменено.Если вызывающая сторона намерена отменить запрос после истечения времени ожидания, вызывающая сторона должна вызвать cancel только после перехвата TimeoutException.
Один из подходов, на который часто указывают, заключается в использовании CompletionService, например,

* 1015.*

Вы контролируете, когда опрашивать завершенные фьючерсы, как в другом фоновом потоке, как показано в примере, или непосредственно перед началом новых заданий.

Вы можете проверить это как

Future<String> f = doSomeWork();
try {
    String s = f.get(500, TimeUnit.MILLISECONDS);
    System.out.println("got "+s);
}
catch(TimeoutException ex) {
    System.out.println("no result after 500ms");
}
catch (ExecutionException ex) {
    System.out.println("failed with "+ex);
}

if(f.cancel(true)) System.out.println("canceled");

f = doSomeWork();
// never calling get() at all

Но, честно говоря, я никогда не понимал, почему такие сложные вещи действительно необходимы.Если вы хотите очистить в нужное время, вы можете использовать

static final ExecutorService MY__EXECUTOR = Executors.newCachedThreadPool();

public static Future<String> doSomeWork() {
    Callable<String> actualJob = () -> {
        Thread.sleep(3000);
        return "hello";
    };
    FutureTask<String> ft = new FutureTask<>(actualJob) {
        @Override
        protected void done() {
            System.out.println("cleanup "+this);
        }
    };
    MY__EXECUTOR.execute(ft);
    return ft;
}

для достижения того же.

Или даже проще

static final ExecutorService MY__EXECUTOR = Executors.newCachedThreadPool();

public static Future<String> doSomeWork() {
    Callable<String> actualJob = () -> {
        Thread.sleep(3000);
        return "hello";
    };
    return MY__EXECUTOR.submit(() -> {
        try {
            return actualJob.call();
        }
        finally {
            // perform cleanup
            System.out.println("cleanup");
        }
    });
}

В любом случае,очистка будет выполнена независимо от того, было ли задание выполнено успешно, не выполнено или отменено.Если использовалось cancel(true), и фактическое задание поддерживает прерывание, очистка также будет выполнена сразу после.

...