Blocking functions and sleep()
  • 2 Minutes to read

Blocking functions and sleep()


Article Summary

A Ra-Ya application runs in a unique thread, and executes concurrent tasks thanks to the Asynchronous I/O Python Library. Methods like setup(), loop() and finish(), listeners, and other internal Ra-Ya stuff are executed as Async Tasks.

As everything is running in the same thread, none of the tasks should take total control of the processing thread. For example, if you run a blocking function in the loop() method, listeners wont be able to be triggered during the function execution.

Async Embedded Functions

Some RayaApplicationBase methods must be always awaited:

await self.sleep()
await self.finish_app()

They won't have any effect if you just call them as sync functions.

Sleep function

The native time.sleep()function should not be used in a Ra-Ya application because it sleeps the thread. If you need the application to sleep for a while, you should use RayaApplicationBase.sleep(time) (self.sleep() in your code), that internally calls the asyncio.sleep()method and allows other taks run while sleeping.

External Async Functions

If you are using libraries that include async funtions, you can await them without problem.

Creating new threads for blocking functions

It's imposible to completely avoid blocking functions, multiple libraries doesn't include async functions and that's not why we are going to stop using them. Some examples of blocking function applications:

  • Calling web services.
  • Executing machine learning models.

A solution to launch blocking function is to create a thread that executes the process and returns the result to the main thread. Just consider that you need to implement the variables syncronization between threads, and don't forget that Ra-Ya embedded methods CAN'T be called from a different thread than the main one.

RayaApplicationBase class provides a simple solution for this. The blocking_function decorator allows you to await a blocking function. It creates a dedicated thread, executes the function, returns the result, and finishes the thread. Consider the following example:

import time
from raya.application_base import RayaApplicationBase

class RayaApplication(RayaApplicationBase):
    async def setup(self):
        self.i = 0
        self.log('Starting application')

    async def loop(self):
        self.log(f'Counter = {self.i}')
        self.i += 1
        ret = self.calc_square(self.i)
        self.log(f'  Return = {ret}')
        if self.i == 10:
            await self.finish_app()
        
    async def finish(self):
        self.log('setup')
    
    def calc_square(self, x):
        time.sleep(1.0)
        return x**2

calc_square() is a blocking function that takes 1 second to return the square of a number. During its execution the thread will be slept and other taks like listener monitors won't work. In conclusion, it makes the application unstable. Now see the solution using the blocking_function decorator:

import time
from raya.application_base import RayaApplicationBase, blocking_function

class RayaApplication(RayaApplicationBase):
    async def setup(self):
        self.i = 0
        self.log('Starting application')

    async def loop(self):
        self.log(f'Counter = {self.i}')
        self.i += 1
        ret = await self.calc_square(self.i)
        self.log(f'  Return = {ret}')
        if self.i == 10:
            await self.finish_app()
        
    async def finish(self):
        self.log('setup')
    
    @blocking_function
    def calc_square(self, x):
        time.sleep(1.0)
        return x**2

As long as you pass all the data you need as arguments, and only use private variables into the function, you won't have synchronization problems.

If you need to access global variables or class members from the blocking function, you can be safe if you create them in the setup() method and don't use them in the loop() method. Consider the following example:

from raya.application_base import RayaApplicationBase, blocking_function
import my_machine_learning_lib as ml

class RayaApplication(RayaApplicationBase):
    async def setup(self):
        self.cameras = self.enable_controller('cameras')
        self.cameras.enable_camera('chest')
        self.ai_model = ml.object_identification_model()

    async def loop(self):
        img = self.cameras.take_snapshot(self.camera_name)
        detected_object = self.idenfity_image(img)
        self.log(f'Identified object: {detected_object}')
        await self.finish_app()
        
    async def finish(self):
        pass
    
    @blocking_function
    def idenfity_image(self, img):
        return self.ai_model.idenfity(img)
    

self.ai_model is created in the main thread, but it's not used there anymore, so you won't get race conditions with the idenfity_image() method.


Was this article helpful?

What's Next