ChannelFuture是Channel的异步I/O操作的结果
在Netty中,所有的I/O操作都是异步的。这就意味着,任何I/O调用都将会立即返回,但是不保证在I/O调用结束时,这个I/O操作的请求已经完成。
不过,它会返回一个ChannelFuture实例给你,里面提供了这个I/O操作的结果或者状态。
一个ChannelFuture的状态不是uncompleted(未完成的)就是completed当开始一个I/O操作时,一个新的ChannelFuture对象就会被创建。这个对象初始化的状态是uncompleted(未完成)--它既不是
succeeded(成功),failed(失败),也不是cancelled(已取消),因为当前I/O操作还没有完成。当I/O操作完成之后,当前ChannelFuture对象就会被
标记上详细的信息:成功、失败或者取消 的具体原因。 请注意:失败 和 取消 两种状态属于已完成的状态。
+---------------------------+
| Completed successfully |
+---------------------------+
+----> isDone() = true |
+--------------------------+ | | isSuccess() = true |
| Uncompleted | | +===========================+
+--------------------------+ | | Completed with failure |
| isDone() = false | | +---------------------------+
| isSuccess() = false |----+----> isDone() = true |
| isCancelled() = false | | | cause() = non-null |
| cause() = null | | +===========================+
+--------------------------+ | | Completed by cancellation |
| +---------------------------+
+----> isDone() = true |
| isCancelled() = true |
+---------------------------+
ChannelFuture中提供了多种方法,以便于 检查当前I/O操作是否已完成、等待当前操作完成、检索当前操作的结果。
同时,它也允许你添加ChannelFutureListener监听器,这样在I/O操作完成时你就可以收到通知。
注意:首选#addListener()方法,而不是#await()方法
无论何时,当你想得到I/O操作完成时的通知时并做接下来的操作的时候,推荐的做法是选择#addListener()方法,而不是#await()方法
#addListener()方法是非阻塞的,它只是简单的将具体的ChannelFutureListener添加到当前的ChannelFuture中,
并且当与当前ChannelFuture关联的I/O操作完成时,I/O线程将会通知这些监听器。由于ChannelFutureListener完全不阻塞,
所以它有最好的性能和资源利用率,但是,如果您不习惯事件驱动的编程,那么实现顺序逻辑可能会很棘手
相比之下,#await()是阻塞操作。一旦调用,调用者线程将阻塞,直到操作完成为止。
使用#await()实现顺序逻辑比较容易,但是调用者线程会不必要地阻塞,直到完成I/O操作为止,并且线程间通知的开销也相对较高。
此外,在特定情况下有死锁的可能性,如下所述:
请勿在ChannelHandler中调用#await()
ChannelHandler中的事件处理程序方法通常是由一个I/O线程调用的。
如果#await()由事件处理程序方法(由I/O线程调用)调用,则它正在等待的I/O操作可能永远不会完成,因为#await()可以阻止正在等待的I/O 操作,这是一个死锁。
永远不要这样做:
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
ChannelFuture future = ctx.channel().close();
future.awaitUninterruptibly();
// Perform post-closure operation
// ...
}
可以这样做:
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
ChannelFuture} future = ctx.channel().close();
future.addListener(new ChannelFutureListener() {
public void operationComplete(ChannelFuture future) {
// Perform post-closure operation
// ...
}
});
}
尽管存在上述缺点,但是在某些情况下调用#await()更方便。
在这种情况下,请确保不要在I/O线程中调用#await()。否则,将产生BlockingOperationException异常以防止死锁
不要混淆I/O超时和等待超时
您使用#await(long),#await(long,TimeUnit),#awaitUninterruptible(long)或#awaitUninterruptible(long)指定的超时值,TimeUnit)与I/O 超时完全无关。
如果I/O操作超时,则将来将被标记为“completed with failure(失败完成)”,如上图所示。例如,连接超时应通过特定于传输的选项进行配置:
永远不要这样做:
Bootstrap b = ...;
ChannelFuture f = b.connect(...);
f.awaitUninterruptibly(10, TimeUnit.SECONDS);
if (f.isCancelled()) {
// 用户尝试取消连接
} else if (!f.isSuccess()) {
// 这里可能产生一个NullPointerException
// 因为future可能还没有完成
f.cause().printStackTrace();
} else {
// 成功建立连接
}
可以这样做:
Bootstrap b = ...;
// 配置连接超时选项
b.option({ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000)
ChannelFuture f = b.connect(...);
f.awaitUninterruptibly();
// 现在我们可以确定任务完成了
assert f.isDone();
if (f.isCancelled()) {
//用户尝试取消连接
} else if (!f.isSuccess()) {
f.cause().printStackTrace();
} else {
// 成功建立连接
}