8. Decorators¶
In addition to the modules, objects and shortcuts available, the framework also provides a number of decorators to assist with plug-in development. Decorators are a type of “syntactical sugar” in Python - by decorating a function, it becomes wrapped inside another function, which can perform tasks before the function code runs and after it completes, even affecting the outcome if necessary.
Decorators are written in Python using the @-syntax shown below:
def MyFunction():
@decorator
def FunctionToDecorate():
""" Sub-function code here """
The above example would decorate FunctionToDecorate() with the decorator named @decorator.
Many of the provided decorators do not expose any new functionality. Their main purpose is to automate common code patterns, making things simpler for the developer, allowing more readable code to be written and reducing the possibility of mistakes.
8.1. Function decorators¶
TODO: Brief message, redirect to Plug-in functions
8.2. Multithreading decorators¶
Many of the decorators provided by the framework are designed to assist with multithreaded code and hide many of the complexities from the developer.
TODO: More information
8.2.1. Lock functions (@lock)¶
The @lock decorator acquires a thread lock before running the decorated function, and releases it afterwards. Using @lock makes writing thread-safe code far simpler:
def MyFunction():
# Assume this function is running in a separate thread
""" Do some work here """
# Here, we want to modify a shared object while ensuring no other threads can access it
# while we're using it.
@lock
def ChangeMyDict():
myDict[x] = y
# The @lock decorator acquires a lock based on the name of the decorated function, so
# any other lock functions named ChangeMyDict would be blocked until this one has
# completed.
This decorator has the same results as manually making calls to Thread.Lock() and Thread.Unlock().
8.2.2. Dictionary modifier functions (@modify_dict)¶
The @modify_dict decorator allows multiple threads to access the plug-in’s dictionary safely. It automatically acquires a lock, fetches an item from the dictionary, modifies it, then stores the modified object in the dictionary before finally unlocking the thread.
TODO: More information, code sample
8.2.3. Parallel task sets¶
To further assist with multithreaded programming, the framework provides an easy method of defining a number of tasks and executing them in parallel by grouping them in to task sets. Using task sets, most of the complexities of multithreading are hidden from the developer.
Note
The threading guidelines for each module should still be observed when using task sets.
TODO: Explain the concepts behind @parallelize and @task
Task sets are most commonly defined within a function. Here is a definition of a simple task set:
def MyFunction(sender):
# Initial set up code - creates a dictionary to store results
resultDict = {}
# Define a function using the @parallelize decorator. This tells the framework that the
# function will define a number of tasks to run in parallel.
@parallelize
def MyTaskSet():
# This code runs on the main thread, in sequence with the rest of the enclosing function. It
# can contain any code required for defining the tasks. Here, a simple loop is used to add
# several tasks to the task set.
for num in range(10):
# Here, a function is defined using the @task decorator for each iteration of the enclosing
# loop. The @task decorator tells the framework that this function is a task belonging to
# the enclosing task set. The num and resultDict objects are included in the function
# definition, allowing them to cross thread boundaries.
@task
def MyTask(num = num, resultDict = resultDict):
# This example simply stores the current date & time in the resultDict dictionary
# created earlier. In order to ensure the code that modifies the dictionary is
# thread-safe, it is wrapped inside a lock function (defined using the @lock decorator).
@lock
def MyLock():
resultDict[num] = Datetime.Now()
# The end of the task set definition function (MyTaskSet) has now been reached. At this point,
# the framework will begin executing all tasks defined by the task set. The main thread will
# be blocked until all tasks have completed.
# We're now outside the @parallelize block and back in the normal flow of the program. In this
# example, resultDict has been populated with a number of datetime objects.
Log(resultDict)