如何从F#中的事件设置多个observable?

时间:2017-06-15 04:28:45

标签: f# observable reactive-programming

我正在尝试通过编写连接到Web套接字的程序来学习F#中的Observable模块,监听消息,然后在基于Observables的一组流中处理它们。但是,我很难理解实际行为。

首先,我设置了一个这样的Web套接字:

open System
open System.Net.WebSockets
open System.Threading

let connectFeed =
    let feedUrl = "blah blah"
    let buffer : byte array = Array.zeroCreate 1024
    let segment = ArraySegment(buffer)
    let socketEvent = new Event<string>()

    let task = async {
        let random = Random(DateTime.Now.Millisecond)
        use socket = new ClientWebSocket()
        let! token = Async.CancellationToken
        do! Async.AwaitTask (socket.ConnectAsync(Uri(feedUrl), token))

        while not token.IsCancellationRequested do
            let! result = Async.AwaitTask (socket.ReceiveAsync(segment, token))
            socketEvent.Trigger (Encoding.UTF8.GetString(buffer))
            Array.fill buffer 0 buffer.Length 0uy

    }

    (task, socketEvent.Publish)

let deserializeMsg (raw:string) =
    // returns a MsgType based on the received message

let tryGetData (msg:MsgType) =
    // returns Some data for specific kind of message; None otherwise

[<EntryPoint>]
let main argv =
    let feedProc, feedStream = connectFeed
    let msgStream = feedStream |> Observable.map deserializeMsg

    msgStream |> Observable.subscribe (fun m -> printfn "got msg: %A" m) |> ignore

    let dataStream = feedStream |> Observable.choose tryGetData
    dataStream |> Observable.subscribe (fun d -> printfn "got data: %A" d) |> ignore

    Async.RunSynchronously feedProc
    0

我期待看到如下打印输出:

got msg: { some: "field" }
got msg: { some: "other" }
got msg: { some: "data" }
got data: { // whatever }
got msg: ...
...

相反,只有“收到消息”消息才会显示,即使有消息会导致tryGetData返回Some

这里发生了什么?如何从单个事件中设置多个Observable流?

更新:我已用此更新了我的代码:

let isMsgA msg =
    printfn "isMsgA"
    match msg with
    | MsgA -> true // where MsgA is a member of a DU defined elsewhere, and is the result of deserializeMsg
    | _ -> false

let isStringMsgA msg =
    printfn "isStringMsgA"
    if msg.StartsWith("{ \"type\": \"msga\"") then true else false

[<EntryPoint>]
let main argv =
    let feedProc, feedStream = connectFeed
    let msgStream = feedStream |> Observable.map deserializeMsg

    msgStream 
    |> Observable.filter isMsgA
    |> Observable.subscribe (fun m -> printfn "got msg MsgA")
    |> ignore

    feedStream 
    |> Observable.filter isStringMsgA
    |> Observable.subscribe (fun m -> printfn "got string MsgA")
    |> ignore

我得到一个充满“isStringMsgA”和“得到字符串MsgA”消息的屏幕,但是“isMsgA”和“得到msg MsgA”中只有一个消息。

我很困惑。

这是一个精简,可重现的例子,适合任何有趣的人: https://github.com/aggieben/test-observable

更新2 :看起来我可能会因为deserializeMsg函数中抛出异常而看到此行为。还在挖......

1 个答案:

答案 0 :(得分:2)

我没有看到为什么会发生这种情况的任何明显原因 - 您是否可以向tryGetData添加一些日志记录以检查它获得的输入以及返回的结果?

使用Observable模块时,您构建了处理管道的描述,Observable.subscribe创建了一个具体的侦听器链,用于执行工作并将处理程序附加到主事件源。但是,这些事件并没有被消费掉#34; - 应将它们发送给所有观察员。

例如,尝试使用以下最小的演示:

let evt = Event<int>()

let e1 = evt.Publish |> Observable.choose (fun n -> 
  if n % 2 = 0 then Some "woop!" else None)
let e2 = evt.Publish |> Observable.map (fun n -> n * 10)

e1 |> Observable.subscribe (printfn "E1: %s")
e2 |> Observable.subscribe (printfn "E2: %d")

evt.Trigger(1)
evt.Trigger(2)

如果你运行它,它会打印出预期的结果:

E2: 10
E1: woop!
E2: 20