【问题标题】:.NET cancelling async I/O operations.NET 取消异步 I/O 操作
【发布时间】:2011-11-14 18:04:00
【问题描述】:

在这种特殊情况下,我正在编写一个 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* 方法取消的通用解决方案 - 我只是为您提供了具体的使用场景,以帮助您理解我的问题。

【问题讨论】:

    标签: c# .net asynchronous io


    【解决方案1】:

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

    您可以使用 TaskFactory.FromAsync 重载之一从您的 Begin/End 方法创建任务并使用它。您可以使用此.ContinueWith 重载来指定您自己的 CancellationToken 的延续以停止它。恕我直言,建议尽可能使用 Tasks,因为下一版本的 C# 将基于 Task 构建并支持异步/等待。

    这是一个很好的article 解释常见模式和任务

    备注:

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

    【讨论】:

    • 有趣,我还没想过。你当然有道理。谢谢!
    • 请注意我的评论 - 如果未完成,请按任务处理。
    • @CarstenKönig:据我了解,这对解决原始问题没有帮助; FromAsync 创建 Task1,ContinueWith 创建 Task2,Task2 在 Task1 完成后执行。取消Task2不会取消Task1还是我错了?
    【解决方案2】:

    异步进程完成时调用 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 提供一种在完成之前取消异步进程的方法取决于具体的实现。恕我直言,这是取消异步操作的唯一干净方法。
    【解决方案3】:

    通用 解决方案是对您调用的 BeginXxxx() 方法的任何对象调用 Close() 或 Dispose()。这将导致回调运行,当您调用 EndXxxx() 时,您将获得 ObjectDisposedException。您应该捕获并将其视为“使用结束”信号,立即清理并退出回调。虽然不受欢迎,但使用异常进行流控制是不可避免的。

    对于 TcpListener,使用 Server.Dispose(),比调用 Stop() 效率高一点。

    【讨论】:

    • 啊哈!非常感谢!您能否详细说明 Dispose() 与 Close() - 有什么区别?
    • 没有区别,Close()总是调用Dispose()。在 .NET 框架设计会议上,有一些痛苦和折磨。
    • 对不起,我的意思是 TcpListener.Stop() 与 TcpListener.Dispose()。似乎 Dispose() 是明确实现的,所以我认为这是因为用户主要使用 Stop()。那么这里有什么区别呢?
    • @Hans 这有点倒退,基于其他实现永远不会猜到。通常关闭调用。
    • Stop() 也会释放套接字,但会创建一个新的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-06-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-04-14
    • 2011-03-14
    相关资源
    最近更新 更多