关于 F# 中的异步的困惑
Confusion about async in F#
我正在用 F# 做一些试验,我写了一个 class 来侦听传入的 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
块中的代码,所以真的有必要使用Task based client.ReceiveAsync()
吗?
listenAsync2
通过在 TP 线程上使用阻塞调用实现与 listenAsync1
相同的结果。
listenAsync3
使用稍微不同的方式再次递归启动侦听器。
listenAsync4
使用循环。它表达的意图很清楚,但并不是那么漂亮。
在 F# 中使用基于任务的异步是否有优势?当包裹在类似于 C# 中的 Task.Run(..)
的异步计算表达式中时,它似乎是多余的。
哪些方法(如果有的话!)通常被认为是最佳实践,为什么? (也许他们可以排名?)
当你使用阻塞调用时,你占用了线程。也就是说,线程是 "busy" 你的调用,不能分配给其他工作。
另一方面,当你等待一个任务时,你完全放弃了控制,线程可以自由地去做其他事情。
实际上,这种区别将体现在您的应用程序无法扩展到大量线程。也就是说,如果您同时进行两次调用,那么您就占用了两个线程。四个调用 - 四个线程。等等。有人可能会争辩说,这种排序打败了 "async".
的整个想法
另一方面,如果您使用可等待任务同时进行多个调用,您的应用程序可能根本不消耗任何线程(当调用正在进行时)!
正因为如此,所有三个阻塞版本都明显逊色。使用第一个。
更新:您可能还想看看this answer。
我正在用 F# 做一些试验,我写了一个 class 来侦听传入的 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
块中的代码,所以真的有必要使用Task based client.ReceiveAsync()
吗?
listenAsync2
通过在 TP 线程上使用阻塞调用实现与 listenAsync1
相同的结果。
listenAsync3
使用稍微不同的方式再次递归启动侦听器。
listenAsync4
使用循环。它表达的意图很清楚,但并不是那么漂亮。
在 F# 中使用基于任务的异步是否有优势?当包裹在类似于 C# 中的 Task.Run(..)
的异步计算表达式中时,它似乎是多余的。
哪些方法(如果有的话!)通常被认为是最佳实践,为什么? (也许他们可以排名?)
当你使用阻塞调用时,你占用了线程。也就是说,线程是 "busy" 你的调用,不能分配给其他工作。
另一方面,当你等待一个任务时,你完全放弃了控制,线程可以自由地去做其他事情。
实际上,这种区别将体现在您的应用程序无法扩展到大量线程。也就是说,如果您同时进行两次调用,那么您就占用了两个线程。四个调用 - 四个线程。等等。有人可能会争辩说,这种排序打败了 "async".
的整个想法
另一方面,如果您使用可等待任务同时进行多个调用,您的应用程序可能根本不消耗任何线程(当调用正在进行时)!
正因为如此,所有三个阻塞版本都明显逊色。使用第一个。
更新:您可能还想看看this answer。