F# F中异步的混淆#

F# F中异步的混淆#,f#,F#,我正在用F#做一点实验,我编写了一个类来监听传入的UDP数据包,打印它们,然后继续监听 我有四种不同的实现都可以实现这一点 type UdpListener(endpoint:IPEndPoint) = let client = new UdpClient(endpoint) let endpoint = endpoint let rec listenAsync1() = async { let! res = client.R

我正在用F#做一点实验,我编写了一个类来监听传入的UDP数据包,打印它们,然后继续监听

我有四种不同的实现都可以实现这一点

type UdpListener(endpoint:IPEndPoint) = 
    let client = new UdpClient(endpoint)
    let endpoint = endpoint

    let rec listenAsync1() = 
        async {
            let! res = client.ReceiveAsync() |> Async.AwaitTask
            res.Buffer |> printfn "%A"
            return! listenAsync1()
        }

    let rec listenAsync2() = 
        async {
            let res = client.Receive(ref endpoint)
            res |> printfn "%A"
            do! listenAsync2()
        }

    let rec listenAsync3() = 
        async {
            let res = client.Receive(ref endpoint)
            res |> printfn "%A"
            listenAsync3() |> Async.Start
        }

    let rec listenAsync4() = 
        async {
            while true do
                let res = client.Receive(ref endpoint)
                res |> printfn "%A"
        }

    member this.Start() =
        listenAsync1() |> Async.Start
listenAsync1
尝试利用
client.ReceiveAsync()
返回的等待值,并使用递归重新侦听。我觉得这种方法最实用

但是,异步计算表达式实际上会在TP线程上运行
async
块中的代码,因此是否确实需要使用基于任务的
client.ReceiveAsync()

listenAsync2
通过在TP线程上使用阻塞调用,实现与
listenAsync1
相同的结果

listenAsync3
使用稍微不同的方式递归地再次启动侦听器

listenAsync4
使用循环。它非常清楚地说明了意图,但实际上并没有那么清楚


在F#中使用基于任务的异步是否有优势?当包装在异步计算表达式中时,它似乎是多余的,该表达式类似于C#中的
Task.Run(..)


以下哪种方法(如果有的话!)通常被认为是最佳实践,为什么?(也许可以对它们进行排名?

使用阻塞调用时,占用线程。也就是说,线程正忙于您的呼叫,无法分配给其他工作。
另一方面,当您等待一个任务时,您完全放弃了控制,线程可以自由地做其他事情

在实践中,这种区别将在应用程序无法扩展到大量线程时表现出来。也就是说,如果您同时进行两次调用,则占用了两个线程。四个调用-四个线程。等等有人可能会说,这种分类否定了“异步”的整个概念。
另一方面,如果您使用等待任务同时进行多个调用,那么您的应用程序可能根本不消耗线程(在调用进行中)

因此,所有三种阻塞版本都明显较差。使用第一个


更新:您可能还想看看。

但是,将非阻塞调用放入异步块不会产生一个线程,尽管只是简单地启动非阻塞调用?取决于如何实现
ReceiveAsync
。任何“sane”实现都将使用相同的线程传递完成延续(如.NET中的旧异步模式)启动接收操作。然后,完成延续通常由线程池执行。@lejon-我认为异步=线程这一点经常会引起混淆。这不一定是真的。IO操作通常通过硬件控制器进行,这意味着CPU在等待专用硬件完成任务时不会阻塞。