Akka.net F#有状态的演员等待multipe FileSystemWatcher Observable事件

时间:2015-12-16 10:33:54

标签: asynchronous f# filesystemwatcher observable akka.net

我是F#以及Akka.Net的新手并试图通过以下方式实现以下目标:

我想创建一个接收文件位置的actor(Tail),然后使用FileSystemWatcher和一些Observables侦听该位置的事件,将它们作为消息转发给其他actor进行处理。

我遇到的问题是,监听事件的代码一次只能获取一个事件而忽略所有其他事件。例如如果我将20个文件复制到被监视的目录中,它似乎只发送其中1个的事件。

这是我的演员代码:

module Tail

open Akka
open Akka.FSharp
open Akka.Actor
open System
open Model
open ObserveFiles
open ConsoleWriteActor

let handleTailMessages tm =
    match tm with
        | StartTail (f,r) ->
            observeFile f consoleWriteActor |!> consoleWriteActor

    |> ignore

let spawnTail =
    fun (a : Actor<IMessage> )  -> 
    let rec l (count : int) = actor{

        let! m = a.Receive()
        handleTailMessages m
        return! l (count + 1)
    } 
    l(0) 

这里是侦听事件的代码:

module ObserveFiles
open System
open System.IO
open System.Threading
open Model
open Utils
open Akka
open Akka.FSharp
open Akka.Actor



let rec observeFile (absolutePath : string) (a : IActorRef )  = async{

    let fsw = new FileSystemWatcher(
                        Path = Path.GetDirectoryName(absolutePath), 
                        Filter = "*.*",
                        EnableRaisingEvents = true, 
                        NotifyFilter = (NotifyFilters.FileName ||| NotifyFilters.LastWrite ||| NotifyFilters.LastAccess ||| NotifyFilters.CreationTime ||| NotifyFilters.DirectoryName)
                        )

    let prepareMessage  (args: EventArgs) =
        let text = 
            match box args with
            | :? FileSystemEventArgs as fsa ->
                match fsa.ChangeType with
                | WatcherChangeTypes.Changed -> "Changed " + fsa.Name
                | WatcherChangeTypes.Created ->  "Created " + fsa.Name
                | WatcherChangeTypes.Deleted -> "Deleted " + fsa.Name
                | WatcherChangeTypes.Renamed -> "Renamed " + fsa.Name
                | _ -> "Some other change " + fsa.ChangeType.ToString()
            | :? ErrorEventArgs as ea -> "Error: " + ea.GetException().Message
            | o -> "some other unexpected event occurd" + o.GetType().ToString()
        WriteMessage text 


    let sendMessage x = async{  async.Return(prepareMessage x) |!> a
                                return! observeFile absolutePath a }

    let! occurance  = 
        [
        fsw.Changed |> Observable.map(fun x -> sendMessage (x :> EventArgs));
        fsw.Created |> Observable.map(fun x -> sendMessage (x :> EventArgs));
        fsw.Deleted |> Observable.map(fun x -> sendMessage (x :> EventArgs));
        fsw.Renamed |> Observable.map(fun x -> sendMessage (x :> EventArgs));
        fsw.Error |> Observable.map(fun x -> sendMessage (x :> EventArgs));
        ] 
        |> List.reduce Observable.merge
        |> Async.AwaitObservable

    return! occurance
}

到目前为止需要花费相当多的黑客,关于我如何改变它的任何建议,以便它在演员运行时拾取和处理所有事件将非常感激。

1 个答案:

答案 0 :(得分:4)

在设计这样的任务时,我们可以将其拆分为以下组件:

  1. 创建负责接收所有消息的经理 - 它的主要作用是响应传入的目录侦听请求。一旦请求进入,它就会创建一个负责在此特定目录下进行侦听的子actor。
  2. Child actor负责管理FileSystemWatcher特定路径。它应该订阅传入事件并将它们作为消息重定向到负责接收更改事件的actor。它应该在关闭时释放一次性资源。
  3. 负责接收更改事件的Actor - 在我们的示例中,通过在控制台上显示它们。
  4. 示例代码:

    open Akka.FSharp
    open System
    open System.IO
    
    let system = System.create "observer-system" <| Configuration.defaultConfig()
    
    let observer filePath consoleWriter (mailbox: Actor<_>) =    
        let fsw = new FileSystemWatcher(
                            Path = filePath, 
                            Filter = "*.*",
                            EnableRaisingEvents = true, 
                            NotifyFilter = (NotifyFilters.FileName ||| NotifyFilters.LastWrite ||| NotifyFilters.LastAccess ||| NotifyFilters.CreationTime ||| NotifyFilters.DirectoryName)
                            )
        // subscribe to all incoming events - send them to consoleWriter
        let subscription = 
            [fsw.Changed |> Observable.map(fun x -> x.Name + " " + x.ChangeType.ToString());
             fsw.Created |> Observable.map(fun x -> x.Name + " " + x.ChangeType.ToString());
             fsw.Deleted |> Observable.map(fun x -> x.Name + " " + x.ChangeType.ToString());
             fsw.Renamed |> Observable.map(fun x -> x.Name + " " + x.ChangeType.ToString());]
                 |> List.reduce Observable.merge
                 |> Observable.subscribe(fun x -> consoleWriter <! x)
    
        // don't forget to free resources at the end
        mailbox.Defer <| fun () -> 
            subscription.Dispose()
            fsw.Dispose()
    
        let rec loop () = actor {
            let! msg = mailbox.Receive()
            return! loop()
        }
        loop ()
    
    // create actor responsible for printing messages
    let writer = spawn system "console-writer" <| actorOf (printfn "%A")
    
    // create manager responsible for serving listeners for provided paths
    let manager = spawn system "manager" <| actorOf2 (fun mailbox filePath ->
        spawn mailbox ("observer-" + Uri.EscapeDataString(filePath)) (observer filePath writer) |> ignore)
    
    manager <! "testDir"