0

This program work fine:

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    

But when I put the same code in the async block when I want to deal with tcp client:

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()    

the timer function will not quit when it have run three times.I want to know why?Thanks

wang kai
  • 1,673
  • 3
  • 12
  • 21

1 Answers1

1

I first want to mention a few things, instead of using Console.WriteLine("a"), just use printfn "a". Secondly, the snippet of code you gave does not terminate, so if you try it in FSI, it will continue running after the main thread finishes. This is likely not an issue in a console app. To answer your question, it has to do with async workflow. If you like in this article: Async Programming, you'll notice that they spawn the async computation as a child and then perform an async sleep to give the child a chance to start. This has to do with the way tasks are scheduled. .NET Frameworks use a "work-first" policy. Continuations typically don't get executed until a blocking event forces the thread to give up the current task. This is how I got the timer event to run:

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)

Note that I used StartChild. I recommend this because of the facts noted here: Async.Start vs. Async.StartChild. A child async task does not need to be given its own cancellation token. Instead it inherits from its parent. So, if I had assigned a cancellation token to the startLoopAsync(), I could cancel that task and all children would cancel as well. Lastly, I recommend keeping a handle on timer in case you ever need to stop that re-occurring event. Not keeping a handle would result in not being able to stop it without killing the process. That is what Thread.Sleep(5000) was for. To show that after the async tasks finish, the timers keep triggering events until the process dies (which requires killing FSI if you use that to test).

I hope this answers your question,
Cheers!

Community
  • 1
  • 1
czifro
  • 784
  • 7
  • 24