为什么Threading.Timer不能在异步块中工作?

时间:2017-04-19 09:39:44

标签: asynchronous f#

这个程序工作正常:

let mutable inc =0

let a(o:obj)=
  let autoEvent=o :?> AutoResetEvent
  Console.WriteLine("a")
  inc<-inc+1
  if inc=3 then
     autoEvent.Set()|>ignore

let autoEvent=new AutoResetEvent(false)
let timer=new Timer(a,autoEvent,0,2000)
autoEvent.WaitOne()|>ignore    

但是当我想处理tcp客户端时,我在异步块中放入相同的代码:

let mutable inc =0

let a(o:obj)=
  let autoEvent=o :?> AutoResetEvent
  Console.WriteLine("a")
  inc<-inc+1
  if inc=3 then
     autoEvent.Set()|>ignore

let listener=new TcpListener(IPAddress.Parse("127.0.0.1"),2000)

let private loop(client:TcpClient,sr:StreamReader,sw:StreamWriter)=
  async{
    let autoEvent=new AutoResetEvent(false)
    let timer=new Timer(a,autoEvent,0,2000)
    autoEvent.WaitOne()|>ignore
 }

let private startLoop()=
  while true do
    let client=listener.AcceptTcpClient()
    let stream=client.GetStream()
    let sr=new StreamReader(stream)
    let sw=new StreamWriter(stream)
    sw.AutoFlush<-true
    Async.Start(loop(client,sr,sw))|>ignore

listener.Start()
startLoop()
listener.Stop()    

计时器功能在运行三次时不会退出。我想知道原因吗?谢谢

1 个答案:

答案 0 :(得分:1)

我首先想提一些事情,而不是使用Console.WriteLine("a"),只需使用printfn "a"即可。其次,您提供的代码片段不会终止,因此如果您在FSI中尝试它,它将在主线程完成后继续运行。这可能不是控制台应用程序中的问题。要回答您的问题,它与异步工作流程有关。如果你喜欢这篇文章:Async Programming,你会注意到它们会像孩子一样产生异步计算,然后执行异步睡眠以给孩子一个开始的机会。这与任务的安排方式有关。 .NET框架使用“先工作”策略。在阻塞事件迫使线程放弃当前任务之前,通常不会执行Continuations。这就是我运行计时器事件的方式:

open System
open System.Threading

let mutable inc =0

let a(o:obj)=
  let autoEvent=o :?> AutoResetEvent
  printfn "a"
  inc<-inc+1
  if inc=3 then
    printfn "hit 3!"
    //autoEvent.Set()|>ignore

let private loop i =
  async{
    printfn "Started as child..."
    let aWrap(o:obj) = // so that we can see which child prints
      printfn "%d" i
    let autoEvent=new AutoResetEvent(false)
    let timer=new Timer(aWrap,autoEvent,0,2000)
    autoEvent.WaitOne()|>ignore
  }

let startLoopAsync() =
  async {
    let children =
      [1..3]
      |> List.map(fun i ->
        Async.StartChild(loop i) // start as child
      )
    do! Async.Sleep 100 // give chance for children to start
    children
    |> List.iter (Async.RunSynchronously >> ignore) // wait for all children
  }


startLoopAsync() |> (Async.RunSynchronously >> ignore) // wait for async loop start
Thread.Sleep(5000)

请注意,我使用了StartChild。我建议这样做是因为这里提到的事实:Async.Start vs. Async.StartChild。子异步任务不需要为其提供自己的取消令牌。相反,它继承自其父级。所以,如果我已经为startLoopAsync()分配了取消令牌,我可以取消该任务,所有孩子也会取消。最后,我建议在timer上保留句柄,以防您需要停止重新发生的事件。不保留句柄会导致无法在不杀死进程的情况下阻止它。这就是Thread.Sleep(5000)的用途。为了表明异步任务完成后,定时器会一直触发事件,直到进程终止(如果您使用它来测试,则需要终止FSI)。

我希望这能回答你的问题,
干杯!