.NET 取消异步 I/O 操作

4
在这个特定的情况下,我正在编写一个TCP/IP服务器,并使用TcpListener.BeginAcceptTcpClient()来接受传入的连接。服务器逻辑实现在一个实现IDisposable接口的单个类中:当调用Dispose()时,我想取消BeginAcceptTcpClient()操作。
现在更一般地说:在使用.NET的异步I/O模型(Begin*和End*)时,如何取消操作?
我的想法是这样的:
private volatile bool canceledFlag = false;

this.listener.BeginAcceptTcpClient(asyncResult =>
{
    if(this.canceledFlag) return;

    // ...
}, null);

Dispose()会将标志设置为true,并调用this.listener.Stop()。 但是我记得阅读过,对于每个Begin*调用都必须有一个匹配的End*调用,否则会发生不好的事情。

那么我该怎么做呢?请注意,我正在寻找适用于Begin*和End*方法取消的通用解决方案 - 我只是提供了具体使用场景以帮助您理解我的问题。

3个回答

7
一般解决方案是调用您调用了BeginXxxx()方法的任何对象的Close()或Dispose()。这将导致回调运行,当您调用EndXxxx()时,您将收到ObjectDisposedException。您应该捕获并将其视为“使用结束”信号,立即清理并退出回调。虽然不被赞成,但使用异常进行流程控制是不可避免的。
对于TcpListener,请使用Server.Dispose(),比调用Stop()更有效率。

啊哈!非常感谢!您能否详细解释一下Dispose()和Close()之间的区别? - ShdNx
1
没有区别,Close()总是调用Dispose()。在.NET框架设计会议上,这引起了一些痛苦和折磨。 - Hans Passant
@好吧,这可能不是非常相关,但以 Stream 类为例。它是许多派生类的基类。其 Close() 方法调用 Dispose()。 - Hans Passant
@Hans:感谢你的回答!我的问题是,你写的使用 Server.Dispose() 比 Stop() 更有效率。为什么?这两者之间有什么区别? - ShdNx
1
Stop() 方法也会释放套接字,但会创建一个新的。 - Hans Passant
显示剩余3条评论

1

当异步进程完成时,将调用AsyncCallback。它允许异步线程重新加入原始线程,并且任何异常都可以冒泡上来(而不是在后台丢失)。它还允许您捕获任何返回值。

通常,您会按照以下方式使用Begin / End模式:

  Action action = () = > DoSomething();
  action.BeginInvoke(action.EndInvoke, null);

或者

 Action action = () = > DoSomething();
 action.BeginInvoke(OnComplete, null);

 private void OnComplete(IAsyncResult ar)
 {
     AsyncResult result = (AsyncResult)ar;
     var caller = (Action)result.AsyncDelegate;
     caller.EndInvoke();

     // do something else when completed
 }

在异步过程完成之前提供取消方式是由具体实现来决定的。通常情况下,这可以通过实现CancelAsync方法来实现,在内部提前停止任务。对于TcpListener,您只需调用listener.Server.Dispose();即可。

 TcpListener listener = new TcpListener(port);
 IAsyncResult = listener.BeginAcceptTcpClient(listener.EndAcceptTcpClient, null);

 // on dispose
 listener.Server.Dispose();

+1 for 具体实现需要提供在异步过程完成之前取消异步操作的方法。 在我看来,这是取消异步操作的唯一干净的方式。 - Korexio

1

我推荐的替代方案是不使用async-Begin-End模式,而是使用Tasks。

您可以使用TaskFactory.FromAsync中的一个重载从Begin/End方法创建任务并使用它。您可以使用.ContinueWith中的一个重载来指定自己的CancellationToken以停止它。在我看来,尽可能使用Tasks是推荐的,因为下一个版本的C#将基于Task构建其async/await支持。

这里有一篇很好的文章,解释了常见的模式和Tasks。

备注:

在我的原始答案中,我说您可以使用.Dispose来终止任务,但这可能会导致严重问题,因为您只能在已完成状态下处理任务。


有趣,我没有想过那个。你肯定有道理。谢谢! - ShdNx
请注意我的备注 - 如果任务没有完成,对任务进行Dispose可能会有问题。 - Random Dev
据我所知,这并不能解决原始问题;FromAsync创建了Task1,ContinueWith创建了Task2,Task2在Task1完成后执行。取消Task2并不会取消Task1,我错了吗? - Korexio

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接