【问题标题】:How to do chained callbacks in F#?如何在 F# 中进行链式回调?
【发布时间】:2010-11-30 21:19:07
【问题描述】:

在 C# 中,我使用 TcpListener/TcpClient 的异步版本,并且我通过回调方法链接这些版本,以便在回调完成时发布另一个 Accept/Read。这是一个示例(未经测试):

    public void Start()
    {
        TcpListener listener = new TcpListener(IPAddress.Any, 3000);
        listener.Start();

        PostAccept(listener);
    }

    private void PostAccept(TcpListener listener)
    {
        listener.BeginAcceptTcpClient(AcceptCallback, listener);
    }

    private void AcceptCallback(IAsyncResult ar)
    {
        var listener = ar.AsyncState as TcpListener;

        if (listener == null)
        {
            return;
        }

        // get the TcpClient and begin a read chain in a similar manner

        PostAccept(listener);
    }

我的问题是如何在 F# 中建模类似的东西?我会使用 async 关键字吗? BeginAcceptTcpClient、BeginRead 等之后的代码本质上是回调函数中将执行的代码吗?例如,这是否正确?

let accept(listener:TcpListener) = async {      
  let! client = listener.AsyncAcceptTcpClient()    
  // do something with the TcpClient such as beginning a Read chain
  accept(listener)
  return()
}    

上面的代码不起作用,因为没有定义accept,并且在技术上将其标记为递归是不正确的,因为它不是递归方法?

【问题讨论】:

  • 只是想说明C#代码可以StackOverflow;为避免这种情况,您需要检查 ar.CompletedSynchronously 并且...呃...在 C# 中执行此操作是一场噩梦。很高兴你有 F#。 :)
  • 但为了完整起见,请参阅blogs.msdn.com/mjm/archive/2005/05/04/414793.aspx 以获得完全正确的 C# 代码。

标签: f# asynchronous tcplistener


【解决方案1】:

@kvb 的回答是正确且惯用的。

还想指出您可以使用(喘气)循环:

let accept(t:TcpListener) =
    let completed = ref false 
    async {
        while not(!completed) do 
            let! client = t.AsyncAcceptTcpClient()
            if client <> null then
                Blah(client)
            else
                completed := true
    }

(那是在我的浏览器中输入代码,希望它能编译)。这很好,因为您当然不能在 C# 代码中使用循环(它必须通过开始/结束回调跨越多个方法)。

【讨论】:

  • 这真的很酷,虽然你提到的有点吓人:)
【解决方案2】:

我不确定您所说的“这不是一种递归方法”是什么意思;如果您从其自己的定义中引用一个函数,那么它是一个递归函数。我对 Sockets 类没有太多经验,但也许这些方面的东西是您正在寻找的?

let rec accept(t : TcpListener) =
  async {
    let! client = t.AsyncAcceptTcpClient()

    // do some stuff with client here

    do! accept(t)
  }

【讨论】:

  • 我想我的问题更多是关于语义的。 C#等语言中的递归方法最终会导致堆栈溢出错误。在我的 C# 代码示例中,它不是递归的,也不会导致堆栈溢出。在 F# 中,'rec' 关键字是否意味着它会以某种方式被递归调用,或者它只是为方法本身提供方法签名?我想我可以通过 ildasm 找出答案,但我很好奇是否有人知道。
  • @esac:rec 关键字允许函数的定义引用函数本身。就编译策略而言,这可能会或可能不会导致生成递归方法(例如,单个尾递归函数可能会编译为循环而不是递归方法)。任何尾递归方法都应该以不会导致堆栈溢出的方式编译(即使编译为递归方法,它也会使用IL中的.tail指令来防止溢出)。请注意,这适用于 VS2010 中的 Release 模式,但可能不适用于 Debug 模式。
猜你喜欢
  • 2012-09-09
  • 1970-01-01
  • 2020-02-27
  • 2018-02-17
  • 1970-01-01
  • 2013-10-24
  • 2018-05-10
  • 1970-01-01
  • 2022-07-12
相关资源
最近更新 更多