gpt4 book ai didi

asynchronous - 从 F# 异步工作流中删除命令式代码

转载 作者:行者123 更新时间:2023-12-04 18:46:03 25 4
gpt4 key购买 nike

我正在为 Logitech Media Server(以前称为 Squeezebox Server)编写一个控制应用程序。

其中一小部分是发现哪些服务器正在本地网络上运行。这是通过向端口 3483 广播一个特殊的 UDP 包并等待回复来完成的。如果在给定时间后没有服务器回复(或首选服务器回复),应用程序应停止监听。

我让它在 C# 中工作,使用 C# 5 的 async/await 功能,但我很想知道它在 F# 中的外观。我有以下功能(或多或少直接从 C# 翻译过来):

let broadCast (timeout:TimeSpan) onServerDiscovered = async {
use udp = new UdpClient ( EnableBroadcast = true )
let endPoint = new IPEndPoint(IPAddress.Broadcast, 3483)
let! _ = udp.SendAsync(discoveryPacket, discoveryPacket.Length, endPoint)
|> Async.AwaitTask

let timeoutTask = Task.Delay(timeout)
let finished = ref false
while not !finished do
let recvTask = udp.ReceiveAsync()
let! _ = Task.WhenAny(timeoutTask, recvTask) |> Async.AwaitTask
finished := if not recvTask.IsCompleted then true
else let udpResult = recvTask.Result
let hostName = udpResult.RemoteEndPoint.Address.ToString()
let serverName = udpResult.Buffer |> getServerName
onServerDiscovered serverName hostName 9090
}
discoveryPacket是一个包含要广播的数据的字节数组。 getServerName是在别处定义的函数,它从服务器回复数据中提取人类可读的服务器名称。

所以应用程序调用 broadCast有两个参数,一个超时和一个回调函数,当服务器回复时将被调用。然后,此回调函数可以通过返回 true 或 false 来决定是否结束监听。如果没有服务器回复,或者没有回调返回 true,则该函数在超时到期后返回。

这段代码工作得很好,但我对命令式引用单元格 finished 的使用有些困扰。 .

所以这里的问题是:有没有一种惯用的 F#-y 方法来做这种事情而不转向命令式的阴暗面?

更新

根据下面接受的答案(几乎是正确的),这是我最终得到的完整测试程序:
open System
open System.Linq
open System.Text
open System.Net
open System.Net.Sockets
open System.Threading.Tasks

let discoveryPacket =
[| byte 'd'; 0uy; 2uy; 23uy; 0uy; 0uy; 0uy; 0uy;
0uy; 0uy; 0uy; 0uy; 0uy; 1uy; 2uy; 3uy; 4uy; 5uy |]

let getUTF8String data start length =
Encoding.UTF8.GetString(data, start, length)

let getServerName data =
data |> Seq.skip 1
|> Seq.takeWhile ((<) 0uy)
|> Seq.length
|> getUTF8String data 1


let broadCast (timeout : TimeSpan) onServerDiscovered = async {
use udp = new UdpClient (EnableBroadcast = true)
let endPoint = IPEndPoint (IPAddress.Broadcast, 3483)
do! udp.SendAsync (discoveryPacket, Array.length discoveryPacket, endPoint)
|> Async.AwaitTask
|> Async.Ignore

let timeoutTask = Task.Delay timeout

let rec loop () = async {
let recvTask = udp.ReceiveAsync()

do! Task.WhenAny(timeoutTask, recvTask)
|> Async.AwaitTask
|> Async.Ignore

if recvTask.IsCompleted then
let udpResult = recvTask.Result
let hostName = udpResult.RemoteEndPoint.Address.ToString()
let serverName = getServerName udpResult.Buffer
if onServerDiscovered serverName hostName 9090 then
return () // bailout signalled from callback
else
return! loop() // we should keep listening
}

return! loop()
}

[<EntryPoint>]
let main argv =
let serverDiscovered serverName hostName hostPort =
printfn "%s @ %s : %d" serverName hostName hostPort
false

let timeout = TimeSpan.FromSeconds(5.0)
broadCast timeout serverDiscovered |> Async.RunSynchronously
printfn "Done listening"
0 // return an integer exit code

最佳答案

您可以使用递归函数“功能性地”实现此功能,该函数还生成 Async<'T> 值(在本例中为 Async)。这段代码应该可以工作——它基于你提供的代码——尽管我无法测试它,因为它取决于你代码的其他部分。

open System
open System.Net
open System.Net.Sockets
open System.Threading.Tasks
open Microsoft.FSharp.Control

let broadCast (timeout : TimeSpan) onServerDiscovered = async {
use udp = new UdpClient (EnableBroadcast = true)
let endPoint = IPEndPoint (IPAddress.Broadcast, 3483)
do! udp.SendAsync (discoveryPacket, Array.length discoveryPacket, endPoint)
|> Async.AwaitTask
|> Async.Ignore

let rec loop () =
async {
let timeoutTask = Task.Delay timeout
let recvTask = udp.ReceiveAsync ()

do! Task.WhenAny (timeoutTask, recvTask)
|> Async.AwaitTask
|> Async.Ignore

if recvTask.IsCompleted then
let udpResult = recvTask.Result
let hostName = udpResult.RemoteEndPoint.Address.ToString()
let serverName = getServerName udpResult.Buffer
onServerDiscovered serverName hostName 9090
return! loop ()
}

return! loop ()
}

关于asynchronous - 从 F# 异步工作流中删除命令式代码,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14110518/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com