F# propper 删除 ElapsedEventHandler 处理程序

F# propper remove of ElapsedEventHandler handler

嗨,我有一个问题,我有一个快速程序,每 10 秒执行一次操作,它看起来很简单:

[<EntryPoint>]
let main argv =

    let timer = new Timer(float 10000)
    let OnTimedEvent (frameId:uint32) : ElapsedEventHandler = new ElapsedEventHandler (fun obj args -> printfn "DATE: %A FRAME: %i" DateTime.Now frameId)

    while(true) do
        let keyStroke = Console.ReadKey() 
        if keyStroke.Key.Equals(ConsoleKey.Enter) then
            let frameId = 1u
            timer.AutoReset <- true
            timer.Elapsed.RemoveHandler(OnTimedEvent frameId)
            timer.Elapsed.AddHandler(OnTimedEvent frameId)
            timer.Start();
        else
            printfn "%c pressed" keyStroke.KeyChar
    0 

问题是我无法按原样正确删除处理程序,如果我在它启动后按回车键并每 10 秒给我一条消息,那么这就是我的目标。但是,如果我按回车键 3 次,它会递增并给我 3 条消息,依此类推,但我只想要一条。

另一件事是,如果我从中删除参数,它会完美运行,所以我想问题出在参数上。对此有什么解决方案吗?

您当前实施的问题是每次调用 OnTimedEvent returns 都是 ElapsedEventHandler 的新实例。当你这样称呼它时:

timer.Elapsed.RemoveHandler(OnTimedEvent frameId)

您正在删除以前未注册的新处理程序,因此实际上没有任何反应。当您将代码更改为 add/remove 相同的处理程序时,您将始终使用相同的实例:

let frameId = 1u
let timedHandler = new ElapsedEventHandler (fun obj args -> 
    printfn "DATE: %A FRAME: %i" DateTime.Now frameId)

timer.Elapsed.RemoveHandler(timedHandler)
timer.Elapsed.AddHandler(timedHandler)

现在您没有将 frameId 传递给事件处理程序的好方法。在你的代码中,frameId 总是 1u 所以很难看出你真正想要什么,但你可以让它可变:

let mutable frameId = 1u
let timedHandler = new ElapsedEventHandler (fun obj args -> 
    printfn "DATE: %A FRAME: %i" DateTime.Now frameId)

frameId <- 2u
timer.Elapsed.RemoveHandler(timedHandler)
timer.Elapsed.AddHandler(timedHandler)

也就是说,您并不清楚您要做什么,也许有一种完全不同的方式来做您想做的事情。

一种完全不同的方法是使用 MailboxProcessor 保持当前 frameId 并处理两种类型的消息 - 一种由计时器每 10 秒触发一次,另一种可用于更改帧 ID:

type Message = 
  | Tick 
  | ChangeFrameId of uint32 

let agent = MailboxProcessor.Start(fun inbox -> 
  let rec run frameId = async {
    let! msg = inbox.Receive()
    match msg with
    | ChangeFrameId newId -> 
        return! run newId
    | Tick ->
        printfn "DATE: %A FRAME: %i" DateTime.Now frameId
        return! run frameId }
  run 1u)

let timer = new Timer(float 10000, AutoReset = true)
timer.Elapsed.Add(fun _ -> agent.Post(Tick))
timer.Start()

agent.Post(ChangeFrameId 2u)

此代码重构了您必须存储处理程序的内容,以便可以将其删除。

open System
open System.Timers

[<EntryPoint>]
let main argv =
    let timer = new Timer(float 10000, AutoReset = true)
    let onTimedEvent (frameId: uint32) : ElapsedEventHandler = new ElapsedEventHandler (fun obj args -> printfn "DATE: %A FRAME: %i" DateTime.Now frameId)
    let rec readKey frameId =
        let handler = onTimedEvent frameId
        timer.Elapsed.AddHandler(handler)
        timer.Start()
        let keyStroke = Console.ReadKey() 
        timer.Stop()
        timer.Elapsed.RemoveHandler(handler)
        printfn "%c pressed" keyStroke.KeyChar
        let nextFrameId = 
            if keyStroke.Key.Equals(ConsoleKey.Enter) then
                frameId + 1u
            else
                frameId
        readKey(nextFrameId)
    readKey(1u)
    0 

可能有更好的方法来完成您所追求的,但这回答了您的问题。