我正在爲Logitech媒體服務器(以前稱爲Squeezebox Server)編寫控制應用程序。從F#異步工作流程中刪除命令式代碼
其中的一小部分是發現哪些服務器正在本地網絡上運行。這是通過向端口3483廣播一個特殊的UDP包並等待回覆來完成的。如果沒有服務器在給定時間後回覆(或首選服務器回覆),應用程序應該停止監聽。
我在C#中使用C#5的異步/等待功能,但我很好奇,看看它在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#方式來做這種事情,而不轉向必要的黑暗面?
更新
基於下面的接受的答案(這是非常接近右),這是一個完整的測試程序,我結束了:
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.RunSynchronously'指定5秒超時運行它? –
@Jon Harrop - 因爲在真正的程序中(這只是一個簡單的例子),我不想同步運行發現。 – corvuscorax