I have a situation which resembles the following:
let mutable stopped = false
let runAsync() = async {
while not stopped do
let! item = fetchItemToProcessAsync
match item with
| Some job -> job |> runJobAsync |> Async.Start
| None -> do! Async.Sleep(1000)
}
let run() = Async.Start runAsync
let stop() =
stopped <- true
Now when the stop method is called, I have to stop reading further items from the DB and also wait for the ones that are currently started to finish before returning from this function.
What is the best way to accomplish this? I was thinking of using a counter, (with interlocked APIs) and return from the stop method when the counter reaches to 0.
If there is an alternative way to accomplish this, I would appreciate the guidance. I have a feeling that I could use agents here, but I am not sure if there is any available way to accomplish this with agent or if I still have to write my custom logic to determine that the jobs have completed executing.
take a look at actor-based patterns and MailboxProcessor
basically you can imagine that as a async-queue. If you use a list of running (started with Async.StartChild or Async.StartAsTask) as the parameter for your loop inside the MailboxProcessor you can gracefully handle shutdowns via wait or a CancellationToken)
Here is a quick sample I put together:
Hmm … got some issues with the editor here: it just kills a lot of code when I use the pipe-back operator … grrr
Short explanation: as you can see I always provide the inner loop with the next free job-id and a Map of Id->AsyncChild jobs.
(you can of course implement other/better solutions – the Map is not neccessary in this example but you can extent with a command “Cancell JobNr” or whatever this way)
the Job done message is only used internaly to remove jobs from this map
Quit just checks if the map is empty – if it is no additional work is needed and the Mailboxprocessor quits (return ()) – if it is not empty a new Async-Child is started that just waits 100ms and then resends the Quit-Message
RunJob is rather simple to – it just chains the given job with a post of JobDone into the MessabeboxProcessor and recursivley calls loop with the updated values (nextId is one up, and a new Job is mapped to the old nextId)