F# 编写轮询Windows服务

F# 编写轮询Windows服务,f#,windows-services,backgroundworker,F#,Windows Services,Backgroundworker,我通常用C语言编写Windows服务,但我用F语言尝试一下。对于像这样的轮询服务,我通常使用我编写的类,类似于。它生成一个后台线程,并定期激发一个OnWork方法。(完整代码为。) 在F#中是否有其他更好或更惯用的方法来实现这一点?这可能是编写轮询后台工作类或其内置替代类的更好方法 编辑 这是我根据乔尔的建议想到的 module Async = open System.Diagnostics let poll interval work = let sw = Stopwatch

我通常用C语言编写Windows服务,但我用F语言尝试一下。对于像这样的轮询服务,我通常使用我编写的类,类似于。它生成一个后台线程,并定期激发一个OnWork方法。(完整代码为。)

在F#中是否有其他更好或更惯用的方法来实现这一点?这可能是编写轮询后台工作类或其内置替代类的更好方法

编辑 这是我根据乔尔的建议想到的

module Async =
  open System.Diagnostics

  let poll interval work =
    let sw = Stopwatch()
    let rec loop() =
      async {
        sw.Restart()
        work()
        sw.Stop()
        let elapsed = int sw.ElapsedMilliseconds
        if elapsed < interval then
          do! Async.Sleep(interval - elapsed)
        return! loop()
      }
    loop()

//Example
open System.Threading
let cts = new CancellationTokenSource()
Async.Start(Async.poll 2000 (fun () -> printfn "%A" DateTime.Now), cts.Token)
Thread.Sleep(TimeSpan.FromSeconds(10.0))
cts.Cancel()

我希望有一种方法可以避免可变的
CancellationTokenSource
,但唉。

我可能会尝试在异步工作流中编写一个简单的循环。你可以使用
do!Async.Sleep interval
在轮询之间休眠-这有两个优点:您不会为了让线程与
thread.Sleep
一起处于空闲状态而占用线程,而
可以CancellationToken
传递到
Async.Start
中,code>会自动为您检查取消


另外,如果您的轮询操作涉及网络通信,则您已经处于异步工作流中,因此进行异步网络调用非常简单。

在相关注释中,有人创建了F#服务模板。它可能包含一些好的锅炉板代码,但是它不包含任何关于轮询的内容。我看到了。谢谢我希望我知道如何发布一些改进建议。+1用于用您的解决方案更新您的问题。我按照这些思路思考,但不确定如何发送启动/停止信号(需要重新启动)。
MailboxProcessor
会产生一个好的解决方案吗?如果您的循环处于异步工作流中,您可以创建一个
CancellationTokenSource
,并使用
async.start(myLoop,cts.Token)
启动工作流。要停止,请调用
cts.Cancel()
。若要重新启动,请以与第一次相同的方式再次启动工作流…Joel的建议可能会很有效;或者,您可以显式(或通过控制流隐式)表示MailboxProcessor中的状态机。如果你想强调状态和转换,那么我想我会使用MailboxProcessor和显式状态;如果您想强调工作,并且让状态/转换只是服务必要性的一部分,我可能只使用带有
Start
/
Cancel
的异步循环。但我是在猜测,不是基于这个领域的经验。我根据你的回答,用我的解决方案更新了我的问题。看起来效果不错。非常感谢。@Brian:我倾向于使用
MailboxProcessor
,但我认为在这种情况下,我关心的唯一转换是停止/启动,这可以通过
async
+
CancellationToken
很好地处理。
type MyService() =
  inherit System.ServiceProcess.ServiceBase()

  let mutable cts = new CancellationTokenSource()
  let interval = 2000

  override __.OnStart(_) = 
    let polling = Async.poll interval (fun () ->
      //do work
    )
    Async.Start(polling, cts.Token)

  override __.OnStop() = 
    cts.Cancel()
    cts.Dispose()
    cts <- new CancellationTokenSource()

  override __.Dispose(disposing) =
    if disposing then cts.Dispose()
    base.Dispose(true)