对F#中异步的困惑

时间:2015-05-05 19:07:03

标签: f#

我正在尝试使用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(..)类似。

哪种方法(如果有的话)通常被认为是最佳做法,为什么? (也许他们可以排名?)

1 个答案:

答案 0 :(得分:4)

当您使用阻止调用时,您将占用该线程。也就是说,线程在您的通话中“忙碌”,无法分配给其他工作 另一方面,等待任务时,你完全放弃控制权,线程可以自由地去做其他事情。

实际上,这种区别将在您的应用程序中体现为无法扩展到大量线程。也就是说,如果你一次打两个电话,你就占用了两个线程。四个电话 - 四个线程。等等。有人可能会争辩说,这种做法违背了“异步”的整个想法 另一方面,如果您使用等待任务同时进行多个呼叫,您的应用程序可能根本不会消耗线程(当呼叫在飞行中时)!

因此,所有三种阻止版本都显着劣势。使用第一个。

更新:您可能还想查看this answer