【问题标题】:F# Async.FromBeginEnd calling End function when it shouldn't?F# Async.FromBeginEnd 不应该调用 End 函数?
【发布时间】:2012-07-11 03:31:47
【问题描述】:

我正在使用 F# 做一些低级别的套接字工作,并且正在使一切异步。我有一个套接字,用于使用异步工作流来监听连接以处理它们,因此它使用它来包装 Socket.BeginListen() 和 Socket.EndListen()。

member socket.AsyncAccept () =
    Async.FromBeginEnd( socket.BeginAccept, endOrDisposed socket.EndAccept null )

当我想停止监听时,此刻我正在对其执行 Socket.Close(),这将导致由 Socket.BeginAccept() 启动的异步操作完成。

最初我遇到了一个问题,因为无论 BeginXXX() 启动的操作如何完成,FromBeginEnd() 函数都会调用 EndXXX() 函数。在某些情况下,这会导致 EndXXX() 函数发生 ObjectDisposed 异常,因为在调用它时,Socket 已被关闭并释放。我添加了一个小处理函数,可以过滤掉这些异常:

let endOrDisposed endFunc defaultResult iar = try endFunc iar with | _ -> defaultResult

这可以解决问题,但在调试中运行时不行。我很清楚 Just My Code 选项可用于隐藏异常,但这可能会更频繁地发生在其他一些 IO 操作中,因此我也不想将处理器时间浪费在引发和捕获不应该的异常上一开始就真的在那里。此外,我可能不想隐藏在我确实需要调试的区域中引发其他异常的位置。

我查看了 Async.FromBeginEnd 的代码,无论如何它总是调用 EndXXX() 函数,我不确定这是最好的行为,也许我应该写一个替换它?或者有人对优雅的解决方案有任何其他想法吗?


我刚刚在文档中找到(当然我以前看过)这个:

要取消对 BeginAccept() 方法的挂起调用,请关闭 插座。当异步调用 Close() 方法时 操作正在进行中,提供给 BeginAccept() 的回调 方法被调用。随后调用 EndAccept() 方法将 抛出 ObjectDisposedException 以指示操作已 被取消了。

我仍然不喜欢这个例外,即使这是设计使然。我宁愿找到一种不在已处置对象上调用 EndXXX() 的方法。也许我可以在其中加入一些 CancellationToken 魔法?

【问题讨论】:

    标签: asynchronous f# io


    【解决方案1】:

    我会在这里使用Async.FromBeginEnd 的可选cancelAction 参数:

    type Socket with
      member this.AsyncAccept () =
        let canceled = ref false
        let endAccept iar = if not !canceled then this.EndAccept iar else null
        let cancel () = canceled := true; this.Close ()
        Async.FromBeginEnd (this.BeginAccept, endAccept, cancelAction = cancel)
    

    这样您就可以使用Async 的内置取消功能(当然基于CancellationToken),而无需直接接触CancellationToken。 (即,您应该致电 Socket.Close 取消订阅。)

    【讨论】:

      【解决方案2】:

      需要一种方法来中断 Async.FromBeginEnd 进程,并在套接字关闭的情况下阻止它尝试 EndAccept(这可能会扩展到 Accept 之外的其他操作)。

      我没有调用 Socket.Close(),而是使用了 CancellationTokenSource 并向其注册了一个处理程序,该处理程序将调用 Socket.Close():

      cts.Token.Register (fun () -> listener.Close()) |> ignore
      

      然后不是直接传递 Async.FromBeginEnd EndAccept 函数,而是现在通过一个函数,如果令牌已被取消,该函数将停止调用 EndAccept。

      let endOrDisposed endFunc defaultResult iar = try endFunc iar with | _ -> defaultResult
      let endIfNotCancelled endFunc defaultResult (token:CancellationToken) iar = if token.IsCancellationRequested then defaultResult else endOrDisposed endFunc defaultResult iar
      
      type Socket with
        member socket.AsyncAccept (cancelToken) =
          Async.FromBeginEnd( socket.BeginAccept, endIfNotCancelled socket.EndAccept null cancelToken)
      

      这仍然使用我的函数来过滤和忽略异常(我不认为异常在网络方面是非常异常的,在这个应用程序的情况下,它无论如何都不会做任何响应)。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-01-30
        • 1970-01-01
        • 1970-01-01
        • 2018-12-02
        相关资源
        最近更新 更多