假设我们使用CompletableFuture.runAsync(..)运行执行,并且在runnable中我们有try-with-resources块(我们正在使用一些应该在发生任何事情时关闭的资源),并且在某些时候执行没有完成尝试阻止我们取消可完成的未来...尽管执行停止应该关闭的资源没有关闭AutoClosable的close()没有被调用...
这是一个java问题还是有办法正确地做到这一点?没有hacky变通办法,如使用期货(支持中断等),如果它的预期行为如何处理类似的情况,当不可中断的CompletableFuture被取消时......?
public class AutoClosableResourceTest {
public static class SomeService{
public void connect(){
System.out.println("connect");
}
public Integer disconnect(){
System.out.println("disconnect");
return null;
}
}
public static class AutoClosableResource<T> implements AutoCloseable {
private final T resource;
private final Runnable closeFunction;
private AutoClosableResource(T resource, Runnable closeFunction){
this.resource = resource;
this.closeFunction = closeFunction;
}
public T get(){
return resource;
}
@Override
public void close() throws Exception {
closeFunction.run();
}
}
@Test
public void testTryWithResource() throws InterruptedException {
SomeService service = new SomeService();
CompletableFuture<Void> async = CompletableFuture.runAsync(() -> {
try (AutoClosableResource<SomeService> resource = new AutoClosableResource<>(service, service::disconnect)) {
resource.get().connect();
while (true) {
Thread.sleep(1000);
System.out.println("working...");
}
} catch (Exception e) {
e.printStackTrace();
}
});
Thread.sleep(2500);
async.cancel(true);
Thread.sleep(2500);
}
}
这将产生
connect
working...
working...
working...
working...
你可以看到它没有调用cancel()并且打开了资源......
答案 0 :(得分:5)
您似乎很难理解CompletableFuture
的目的是什么。看看its class documentation的第一句话:
可以明确完成的
Future
(设置其值和状态),...
与执行其FutureTask
方法的线程完成的run
不同,CompletableFuture
可由任何线程完成,该线程将在任意时间点设置其值/状态。 CompletableFuture
不知道哪个线程会完成它,甚至不知道当前是否有一个线程正在完成它。
因此CompletableFuture
在取消时不能中断正确的线程。这是其设计的基本部分。
如果您想要一个可以打断的工作线程,最好使用FutureTask
/ ThreadPoolExecutor
。以这种方式安排的任务可能仍然在其末尾完成CompletableFuture
。
答案 1 :(得分:1)
以下代码将陷入无限循环。调用async.cancel将不会与以下循环通信,它希望停止。
while (true) {
Thread.sleep(1000);
System.out.println("working...");
}
测试用例退出是因为插入此循环的线程不是守护程序线程。
用以下代码替换while循环检查,它在每次迭代时检查isCancelled标志。调用CompletableFuture.cancel()会将未来标记为已取消,但它不会中断通过runAsync启动的线程。
while (isCancelled()) {
Thread.sleep(1000);
System.out.println("working...");
}
答案 2 :(得分:0)
您可以使用&#34;完成&#34; CompletableFuture停止线程的方法。
下面是一个显示行为的简单代码:
package com.ardevco;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CompletableFutureTest3 {
public static void main(String[] args) throws Exception {
ExecutorService pool = Executors.newFixedThreadPool(5);
CompletableFuture<Integer> longRunningcompletableFuture = CompletableFuture.supplyAsync(() -> {
for (int i = 0; i < 1; i--) {
System.out.println("i " + i);
sleep();
}
return 1; // we will newer reach this line so the thread will be stuck
});
CompletableFuture<Integer> completor = CompletableFuture.supplyAsync(() -> {
System.out.println("completing the longRunningcompletableFuture");
longRunningcompletableFuture.complete(1000);
System.out.println("completed the longRunningcompletableFuture");
return 10;
});
Thread.sleep(10000);
System.out.println("completor...");
int i = completor.get();
System.out.println("completor i:" + i);
System.out.println("completor...");
System.out.println("completableFutureToBeCompleted2...");
int i2 = longRunningcompletableFuture.get();
System.out.println("completableFutureToBeCompleted2: " + i2);
System.out.println("completableFutureToBeCompleted2...");
}
private static void sleep() {
try {Thread.sleep(1000);}catch (Exception e) {}
}
}
输出:
我0 完成longRunningcompletableFuture 完成了longRunningcompletableFuture 我-1 我-2 我-3 我-4 我-5 我-6 我-7 我-8 我-9 我-10 completor ... completor i:10 completor ... completableFutureToBeCompleted2 ... completableFutureToBeCompleted2:1000 completableFutureToBeCompleted2 ...
答案 3 :(得分:0)
虽然答案标记为正确,但原因却截然不同 - 请参阅CompletableFuture.cancel(mayInterruptIfRunning)
method的文档,并阅读文章CompletableFuture can't be interrupted以更好地了解问题。
我的Tascalate Concurrent库已解决此问题,您的代码更改应为:
从
CompletableFuture<Void> async = CompletableFuture.runAsync(() -> {
...
});
要
Promise<Void> async = CompletableTask.runAsync(() -> {
...
}, someExplicitExecutor);
...并且您将得到预期的行为(执行程序线程被中断,AutoClosable已关闭,async
已完成CancellationException
)。
您可以在my blog
中详细了解该库答案 4 :(得分:0)
我在Java 8 SE中也遇到了这个问题。对我来说,重要的是不要使用第三方库。
cancel( mayInterruptIfRunning )此值在此实现中无效,因为不使用中断来控制处理。
想法是在调用 cancel()时使用 Thread.interrupt(),但仅用于 Runnable >。
/** Enable and disable the interrupt */
private static class Interruptor {
volatile boolean interrupted;
volatile Runnable interrupt;
/** Enable interrupt support */
synchronized boolean start() {
if (interrupted) {
return false;
}
Thread runThread = Thread.currentThread();
interrupt = () -> {
if (runThread != Thread.currentThread()) {
runThread.interrupt();
}
};
return true;
}
/** Interrupt Runnable */
synchronized void interrupt() {
if (interrupted) {
return;
}
interrupted = true;
if (interrupt != null) {
interrupt.run();
interrupt = null;
}
}
/** Disable interrupt support */
synchronized void finish() {
interrupt = null;
}
}
/** CompletableFuture with interrupt support */
public static CompletableFuture<Void> runAsyncInterrupted(Runnable run) {
final Interruptor interruptor = new Interruptor();
Runnable wrap = () -> {
if (!interruptor.start()) { // allow interruption
return; // was canceled before the thread started
}
try {
run.run(); // can be interrupted
} finally {
interruptor.finish(); // can no longer be interrupted
}
};
CompletableFuture<Void> cfRun = CompletableFuture.runAsync(wrap);
// here is caught "CompletableFuture.cancel()"
cfRun.whenComplete((r, t) -> {
if (t instanceof CancellationException) {
interruptor.interrupt();
}
});
return cfRun;
}
使用示例
Runnable mySlowIoRun = () -> {
try {
InputStream is = openSomeResource(); // open resource
try {
// there may be problem (#1) with reading,
// such as loss of network connection
int bt = is.read();
// ..
// .. some code
} finally {
is.close(); // problem (#2): releases any system resources associated with the stream
}
} catch (Throwable th) {
throw new RuntimeException(th);
}
};
CompletableFuture<Void> cf = runAsyncInterrupted(mySlowIoRun);
try {
cf.get(5, TimeUnit.SECONDS); // 5 sec timeout
} catch (Throwable th) {
cf.cancel(true); // cancel with interrupt mySlowIoRun
throw th;
}
答案 5 :(得分:0)
因此,这是我通常如何处理该问题的概述..传递可取消状态,并在打开状态后立即关闭资源。
private static BufferedReader openFile(String fn) {
try {
return Files.newBufferedReader(Paths.get(fn));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
static class Util {
static void closeQuietly(AutoCloseable c) {
if (c == null) return;
try {
c.close();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
static <T extends AutoCloseable, R> R runThenCloseQuietly(T c, Function<T,R> cb) {
try {
return cb.apply(c);
} finally {
closeQuietly(c);
}
}
static <T extends AutoCloseable, R> Optional<R> runThenCloseQuietlyCancellable(BooleanSupplier cancelled
, T c, Function<T,Optional<R>> cb) {
if (c == null) return Optional.empty(); // safe doesn't throw
try {
if (cancelled.getAsBoolean()) return Optional.empty(); // might throw, wrap for safety
return cb.apply(c); // might throw
} finally {
closeQuietly(c); // might throw, but at least we're closed
}
}
private static Optional<String> emptyString() {
return Optional.empty();
}
}
interface Cancellable {
boolean isCancelled();
void cancel();
}
static class CancellableAB implements Cancellable {
private final AtomicBoolean cancelled;
CancellableAB(AtomicBoolean cancelled) {
this.cancelled = cancelled;
}
@Override
public boolean isCancelled() {
return cancelled.get();
}
@Override
public void cancel() {
cancelled.set(true);
}
}
static class CancellableArray implements Cancellable {
private final boolean[] cancelled;
private final int idx;
CancellableArray(boolean[] cancelled) {
this(cancelled, 0);
}
CancellableArray(boolean[] cancelled, int idx) {
this.cancelled = cancelled;
this.idx = idx;
}
@Override
public boolean isCancelled() {
return cancelled[idx];
}
@Override
public void cancel() {
cancelled[idx]=true;
}
}
static class CancellableV implements Cancellable {
volatile boolean cancelled;
@Override
public boolean isCancelled() {
return cancelled;
}
@Override
public void cancel() {
this.cancelled = true;
}
}
/**
* The only reason this is a class is because we need SOME external object for the lambda to check for mutated
* cancelled state.
* This gives the added benefit that we can directly call cancel on the resource.
* We allow a cancellable to be passed in to CHAIN-IN cancellable state. e.g. if cancellation should affect MULTIPLE
* CompletableFuture states, we don't want other promises to tie references to this task.. So the cancellable
* object can be externalized.
*
* Normally you don't need this much genericism, you can directly implement a volatile 'cancel boolean'.
* But this allows you to create a C.F. task as a 3rd party library call - gives maximum flexibility to invoker.
*
*/
static class FooTask {
volatile Cancellable cancelled;
String fileName;
public FooTask(String fileName) {
this.fileName = fileName;
this.cancelled = new CancellableV();
}
public FooTask(String fileName, Cancellable cancelled) {
this.cancelled = cancelled;
}
public boolean isCancelled() {
return cancelled.isCancelled();
}
public void cancel() {
cancelled.cancel();
}
/**
* asynchronously opens file, scans for first valid line (closes file), then processes the line.
* Note if an exception happens, it's the same as not finding any lines. Don't need to special case.
* Use of utility functions is mostly for generic-mapping
* (avoiding annoying double-type-casting plus editor warnings)
*/
CompletableFuture<Optional<Long>> run1() {
return
CompletableFuture.supplyAsync(() -> openFile(fileName))
.thenApplyAsync(c -> { // this stage MUST close the prior stage
if(cancelled.isCancelled() || c == null) return Util.emptyString(); // shouldn't throw
try {
return c
.lines()
.filter(line -> !cancelled.isCancelled())
.filter(line -> !line.startsWith("#"))
.findFirst();
} catch (RuntimeException e) {
Util.closeQuietly(c);
throw new RuntimeException(e);
}
}
)
.thenApplyAsync(oLine -> // this stage doesn't need closing
oLine
.map(line -> line.split(":"))
.map(cols -> cols[2])
.map(Long::valueOf)
)
;
}
/**
* Same as run1 but avoids messy brackets + try-finally
*/
CompletableFuture<Optional<Long>> run2() {
return
CompletableFuture.supplyAsync(() -> openFile(fileName))
.thenApplyAsync(c -> // this stage MUST close the prior stage
Util.runThenCloseQuietly(
c
, r -> cancelled.isCancelled() ? Util.emptyString() // shouldn't throw
: r
.lines()
.filter(line -> !cancelled.isCancelled())
.filter(line -> !line.startsWith("#"))
.findFirst()
))
.thenApplyAsync(oLine -> // this stage doesn't need closing
oLine
.map(line -> line.split(":"))
.map(cols -> cols[2])
.map(Long::valueOf)
)
;
}
/**
* Same as run2 but avoids needing the teneary operator - says Cancellable in func-name so is more readable
*/
CompletableFuture<Optional<Long>> run3() {
return
CompletableFuture.supplyAsync(() -> openFile(fileName))
.thenApplyAsync(c -> // this stage MUST close the prior stage
Util.runThenCloseQuietlyCancellable(
cancelled::isCancelled // lambda here is slightly easier to read than explicit if-statement
, c
, r -> r
.lines()
.filter(line -> !cancelled.isCancelled())
.filter(line -> !line.startsWith("#"))
.findFirst()
))
.thenApplyAsync(oLine -> // this stage doesn't need closing
oLine
.map(line -> line.split(":"))
.map(cols -> cols[2])
.map(Long::valueOf)
)
;
}
}
@Test
public void testFooGood() {
var task = new FooTask("/etc/passwd");
var cf = task.run3();
var oVal = cf.join();
assertTrue(oVal.isPresent());
System.out.println(oVal.get()); // should not throw
}
@Test
public void testFooCancel() {
var task = new FooTask("/etc/passwd");
var cf = task.run3();
task.cancel();
var oVal = cf.join();
assertTrue(oVal.isEmpty());
}