Gather/Scatter operations
=========================

In many situations it is necessary to query data from dozens or hundreds of sensors. Thanks to the asynchronous
functionality discussed in :ref:`Async` this is possible and is limited only by memory and
network bandwidth. An example of a practical application of gather/scatter operations is that of an advanced lighting
system that needs to rapidly query large numbers of sensors, make control decisions, and then distribute updated
commands to a large number of lighting fixtures. Consider a list of connections that have already been connected and are
stored in a variable ``c_list``. Each connection is to a service that has the function ``ReadSensor()`` that returns
some important data. The following example will query each sensor concurrently and call the
handler when all the sensors have been queried.

.. code:: python


   global_err=[]
   global_data=[]
   ev=threading.Event()


   def read_finished(data, err):
       # "data" now contains a list of data
       # "err" contains a list with each element containing "None" or the exception that occurred for that read

       # Store data in global variables
       global global_err, global_data
       global_err=err
       global_data=data

       # Notify "main()" that the read is complete
       ev.set()


   # c_list contains a list of connections created with RobotRaconteur.s.ConnectService
   def start_read(c_list, handler, timeout):
       N=len(c_list}
       keys=[]
       keys_lock=threading.Lock()
       ret=[None]*N
       err=[None]*N

       # cSpell:ignore erri
       def h(key, d, erri):
           done=False
           with keys_lock:
               if (erri is not None):
                   err[key]=erri
               else:
                   ret[key]=d
               keys.remove(key)

               if (len(keys)==0): done=True

           if (done):
               handler(ret,err)

       with keys_lock:
           for i in xrange(N):
               try:
                   c_list[i].async_ReadSensor(functools.partial(h,i),timeout)
                   keys.append(i)
               except Exception as erri:
                   err[i]=erri
           if (len(keys)==0):
               raise Exception("Could not read any sensors")

   def main()

       # Create all the c_list connections here

       # Start the read with a 100 ms timeout
       start_read(c_list,read_finished,0.1)

       # Wait for completion
       ev.wait()

       # Do something with the results
       print global_data
       print global_err
