我有一个数据生成器,它在一个单独的线程中运行,并将生成的数据推送到PipedOutputStream
,并连接到PipedInputStream
。此输入流的引用通过公共API公开,以便任何客户端都可以使用它。 PipedInputStream
包含有限的缓冲区,如果已满,则会阻止数据生成器。基本上,当客户端从输入流中读取数据时,数据生成器会生成新数据。
问题是数据生成器可能会失败并抛出异常。但是当消费者在一个单独的线程中运行时,没有很好的方法可以将异常传递给客户端。
我所做的是捕获该异常并关闭输入流。这将在客户端产生IOException
消息“管道关闭”,但我真的想给客户提供背后的真正原因。
这是我的API的粗略代码:
public InputStream getData() {
final PipedInputStream inputStream = new PipedInputStream(config.getPipeBufferSize());
final PipedOutputStream outputStream = new PipedOutputStream(inputStream);
Thread thread = new Thread(() -> {
try {
// Start producing the data and push it into output stream.
// The production my fail and throw an Exception with the reason
} catch (Exception e) {
try {
// What to do here?
outputStream.close();
inputStream.close();
} catch (IOException e1) {
}
}
});
thread.start();
return inputStream;
}
我有两个想法如何解决这个问题:
IOException
,则客户端可以向API询问原因。close()
方法。然后,流引发的IOException
可以包含该原因作为消息。有更好的想法吗?
答案 0 :(得分:2)
巧合的是,我只是编写了类似的代码来允许GZip压缩流。您不需要扩展PipedInputStream,只需FilterInputStream即可执行并返回包装版本,例如
final PipedInputStream in = new PipedInputStream();
final InputStreamWithFinalExceptionCheck inWithException = new InputStreamWithFinalExceptionCheck(in);
final PipedOutputStream out = new PipedOutputStream(in);
Thread thread = new Thread(() -> {
try {
// Start producing the data and push it into output stream.
// The production my fail and throw an Exception with the reason
} catch (final IOException e) {
inWithException.fail(e);
} finally {
inWithException.countDown();
}
});
thread.start();
return inWithException;
然后InputStreamWithFinalExceptionCheck只是
private static final class InputStreamWithFinalExceptionCheck extends FilterInputStream {
private final AtomicReference<IOException> exception = new AtomicReference<>(null);
private final CountDownLatch complete = new CountDownLatch(1);
public InputStreamWithFinalExceptionCheck(final InputStream stream) {
super(stream);
}
@Override
public void close() throws IOException {
try {
complete.await();
final IOException e = exception.get();
if (e != null) {
throw e;
}
} catch (final InterruptedException e) {
throw new IOException("Interrupted while waiting for synchronised closure");
} finally {
stream.close();
}
}
public void fail(final IOException e) {
exception.set(Preconditions.checkNotNull(e));
}
public void countDown() {complete.countDown();}
}
答案 1 :(得分:2)
这是我的实现,取自上面接受的答案https://stackoverflow.com/a/33698661/5165540,我不使用CountDownLatch records = list(SeqIO.parse("sys.argv[0]", "fasta"))
records.sort(key=lambda x : x.id)
SeqIO.write(records, "sys.arg[0]-sorted.fas", "fasta")
因为如果在编写完成之前InputStream突然关闭会导致死锁全部内容。
我仍然设置了在使用PipedOutpuStream时捕获的异常,并且我在生成线程中创建了PipedOutputStream,使用try-finally-resource模式来确保它被关闭,在供应商中等待直到2个流被传送。
complete.await()
调用代码就像
Supplier<InputStream> streamSupplier = new Supplier<InputStream>() {
@Override
public InputStream get() {
final AtomicReference<IOException> osException = new AtomicReference<>();
final CountDownLatch piped = new CountDownLatch(1);
final PipedInputStream is = new PipedInputStream();
FilterInputStream fis = new FilterInputStream(is) {
@Override
public void close() throws IOException {
try {
IOException e = osException.get();
if (e != null) {
//Exception thrown by the write will bubble up to InputStream reader
throw new IOException("IOException in writer", e);
}
} finally {
super.close();
}
};
};
Thread t = new Thread(() -> {
try (PipedOutputStream os = new PipedOutputStream(is)) {
piped.countDown();
writeIozToStream(os, projectFile, dataFolder);
} catch (final IOException e) {
osException.set(e);
}
});
t.start();
try {
piped.await();
} catch (InterruptedException e) {
t.cancel();
Thread.currentThread().interrupt();
}
return fis;
}
};
因此,当InputStream关闭时,将在PipedOutputStream中发出信号,最终导致“管道关闭”IOException,此时将被忽略。
如果我保留FilterInputStream try (InputStream is = streamSupplier.getInputStream()) {
//Read stream in full
}
中的complete.await()
行,我可能会遇到死锁(PipedInputStream试图关闭,等待close()
,而PipedOutputStream在PipedInputStream上永远等待{{} 1}})