I'm not entirely sure what you want to achieve to be honest as your two 
examples point in slightly different directions:

Here's a way to structure the code so the channel is checked for messages every 
10 mills no matter of there's ongoing async work or not:
    
    
    proc readWork() =
        var msg: Option[Msg] = data.hub.readMsg(Msg)
        while msg.isSome():
          asyncSpawn processWork(msg)
          msg = data.hub.readMsg(Msg)
    
    const pollInterval = 10.millis
    
    while keepGoing():
         waitFor sleepAsync(pollInterval)
         readWork()
    
    
    Run

If you don't want the message queue to be checked while there is async work 
ongoing, the code becomes:
    
    
    proc readWork(): seq[Future[void]] =
        var msg: Option[Msg] = data.hub.readMsg(Msg)
        while msg.isSome():
          result.add processWork(msg)
          msg = data.hub.readMsg(Msg)
    
    while keepGoing():
         let work = readWork()
         if work.len > 0:
           for w in work: waitFor w
         else:
           waitFor sleepAsync(pollInterval)
    
    
    Run

The `hasPendingOperations` approach, as well all above examples, are 
fundamentally flawed though, as is the idea to use `sleep` in general. This is 
part of the reason why chronos doesn't have a `hasPendingOperations` function: 
it promotes a poor pattern of execution that has many gotchas and flaws in 
general and if you're reaching for it something probably went wrong before you 
got to that point.

Every time you introduce a sleep or other forms of polling to make things work, 
that's likely an inefficiency and the only time you would do that is when you 
don't have a choice (because you don't control the source channel for example 
or its notification mechanism is not adapted to the event loop).

The way this is solved in efficient systems is that every message queue / 
channel comes with a signalling mechanism to tell the event loop to wake up 
because there is a new message to process.

In chronos, this is `AsyncEvent` or `ThreadSignal` depending on whether it's a 
multithreaded scenario or not (the latter works for both) - basically, every 
time you put a message on the queue, you `fire` the event so the event loop 
wakes up:
    
    
    var signal = ThreadSignalPtr.new()[]
    
    proc addMessage(m: Msg) =
       data.hub.add m
       # Tell the loop there is new data
       signal.fireSync()
    
    proc readWork() =
        var msg: Option[Msg] = data.hub.readMsg(Msg)
        while msg.isSome():
          asyncSpawn processWork(msg)
          msg = data.hub.readMsg(Msg)
    
    while keepGoing():
        waitFor signal.wait()
        readWork()
    
    
    Run

The above code is optimal in that there is no polling so the code will only do 
work when there's work to do - it'll also never sleep unnecessarily when it's 
already known that there's work arriving on the channel etc.

Of course, you don't really want to be using `asyncCheck` / `asyncSpawn` in a 
production system due to the shoddy exception handling (unless you make sure to 
[catch all exceptions in each 
task](https://status-im.github.io/nim-chronos/error_handling.html#checked-exceptions)),
 but that's a different story.

Reply via email to