F# Async.FromBeginEnd 在不应该调用 End 函数时调用它?



我正在使用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异常,因为在调用它时,套接字已经关闭并被释放。我添加了一个小的处理程序函数,可以过滤掉这些异常:

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

这可以达到目的,但在调试中运行时不会。我很清楚,"仅我的代码"选项可以用来隐藏异常,但这种情况可能会在其他一些IO操作中更频繁地发生,所以我也不希望处理器时间浪费在引发和捕获本来就不应该存在的异常上。此外,我可能不想隐藏其他异常被抛出的位置,因为它们可能是在我确实需要调试的区域中抛出的。

我已经查看了Async.FromBeginEnd的代码,它可以随时调用EndXXX()函数,我不确定这是否是最佳行为,也许我应该为它写一个替代品?或者有人对优雅的解决方案有其他想法吗?


我刚刚在文档中发现(当然我以前也看过):

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

我仍然不喜欢这个例外,即使它是故意的。我更愿意找到一种方法,不在已处理的对象上调用EndXXX()。也许我可以把CancellationToken的魔力融入其中?

我会在这里使用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。)

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

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

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)

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

最新更新