Skip to content

Commit

Permalink
wrote documentation about threading + added named threads and preferr…
Browse files Browse the repository at this point in the history
…ed thread decorator
  • Loading branch information
henrypinkard committed Aug 25, 2024
1 parent bd5bd76 commit 7c66389
Show file tree
Hide file tree
Showing 15 changed files with 450 additions and 134 deletions.
3 changes: 2 additions & 1 deletion docs/extending.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ Extending ExEngine
extending/add_devices
extending/add_events
extending/add_notifications
extending/add_storage
extending/add_storage
extending/threading
11 changes: 11 additions & 0 deletions docs/extending/add_devices.rst
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,17 @@ then open ``_build/html/index.html`` in a web browser to view the documentation.
Advanced Topics
-----------------

Thread Safety and Execution Control
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
ExEngine provides powerful threading capabilities for device implementations, ensuring thread safety and allowing fine-grained control over execution threads. Key features include:

Automatic thread safety for device methods and attribute access.
The ability to specify execution threads for devices, methods, or events using the @on_thread decorator.
Options to bypass the executor for non-hardware-interacting methods or attributes.

For a comprehensive guide on ExEngine's threading capabilities, including detailed explanations and usage examples, please refer to the :ref:threading section.


What inheritance from ``Device`` provides
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Expand Down
114 changes: 104 additions & 10 deletions docs/usage/threading.rst → docs/extending/threading.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
.. _threading:

Threading in ExEngine
=====================

Expand All @@ -24,7 +26,7 @@ Common solutions like single-threaded event loops can limit performance, while i
ExEngine's Solution for Thread-Safe Device Control
--------------------------------------------------

ExEngine addresses the challenge of thread-safe device control by routing all method calls and attribute accesses of `Device` objects through a common thread pool managed by the `ExecutionEngine`. In other words, when a user calls a method on a device, sets an attribute, or gets an attribute, the call is automatically routed to the `ExecutionEngine` for execution.
ExEngine addresses the challenge of thread-safe device control by routing all method calls and attribute accesses of ``Device`` objects through a common thread pool managed by the ``ExecutionEngine``. In other words, when a user calls a method on a device, sets an attribute, or gets an attribute, the call is automatically routed to the ``ExecutionEngine`` for execution.

This allows for simple, seemingly single-threaded user code. That is, users can methods and set attributes in the normal way (e.g. ``device.some_method()``, ``device.some_attribute = value``), from any thread, but the actual execution happens on a thread managed by the executor.

Expand All @@ -36,17 +38,17 @@ This approach ensures thread safety when using devices from multiple contexts wi

While understanding the underlying mechanics isn't essential for regular usage, here's a brief overview:

The core of this solution lies in the `DeviceMetaclass`, which wraps all methods and set/get operations on attributes classes inheriting from `Device` subclasses.
The core of this solution lies in the ``DeviceMetaclass``, which wraps all methods and set/get operations on attributes classes inheriting from ``Device`` subclasses.

When a method is called or an attribute is accessed, instead of executing directly, a corresponding event (like `MethodCallEvent` or `GetAttrEvent`) is created and submitted to the `ExecutionEngine`. The calling thread blocks until the event execution is complete, maintaining the illusion of synchronous operation.
When a method is called or an attribute is accessed, instead of executing directly, a corresponding event (like ``MethodCallEvent`` or ``GetAttrEvent``) is created and submitted to the ``ExecutionEngine``. The calling thread blocks until the event execution is complete, maintaining the illusion of synchronous operation.

In other words, calling a function like:

.. code-block:: python
some_device.some_method(arg1, arg2)
Gets automatically transformed into a ``MethodCallEvent`` object, which is then submitted to the `ExecutionEngine` for execution, and its result is returned to the calling thread.
Gets automatically transformed into a ``MethodCallEvent`` object, which is then submitted to the ``ExecutionEngine`` for execution, and its result is returned to the calling thread.

.. code-block:: python
Expand All @@ -60,13 +62,13 @@ This approach ensures thread safety when using devices from multiple contexts wi
On an executor thread, the event's `execute` method is called:
On an executor thread, the event's ``execute`` method is called:

.. code-block:: python
.. code-block:: python
def execute(self):
method = getattr(self.instance, self.method_name)
return method(*self.args, **self.kwargs)
def execute(self):
method = getattr(self.instance, self.method_name)
return method(*self.args, **self.kwargs)
This process ensures that all device interactions occur on managed threads, preventing concurrent access issues while maintaining a simple API for users.
Expand All @@ -90,9 +92,101 @@ ExEngine also supports named threads for task-specific execution:
engine.submit(readout_event, thread_name="DetectorThread")
engine.submit(control_event, thread_name="HardwareControlThread")
The ExecutionEngine automatically creates the specified threads as needed. You don't need to manually create or manage these threads.

This feature enables logical separation of asynchronous tasks. For instance:

- One thread can be dedicated to detector readouts
- Another can manage starting, stopping, and controlling other hardware

Using named threads enhances organization and can improve performance in multi-task scenarios.
Using named threads enhances organization and can improve performance in multi-task scenarios.



Using the @on_thread Decorator
------------------------------

ExEngine provides a powerful ``@on_thread`` decorator that allows you to specify which thread should execute a particular event, device, or method. This feature gives you fine-grained control over thread assignment without complicating your code.

Importing the Decorator
^^^^^^^^^^^^^^^^^^^^^^^

To use the ``@on_thread`` decorator, import it from ExEngine:

```python
from exengine import on_thread
```

Decorating Events
^^^^^^^^^^^^^^^^^

You can use ``@on_thread`` to specify which thread should execute an event:

.. code-block:: python
@on_thread("CustomEventThread")
class MyEvent(ExecutorEvent):
def execute(self):
# This will always run on "CustomEventThread"
...
Decorating Devices
^^^^^^^^^^^^^^^^^^

When applied to a device class, ``@on_thread`` sets the default thread for all methods of that device:

.. code-block:: python
@on_thread("DeviceThread")
class MyDevice(Device):
def method1(self):
# This will run on "DeviceThread"
...
def method2(self):
# This will also run on "DeviceThread"
...
Decorating Methods
^^^^^^^^^^^^^^^^^^

You can also apply ``@on_thread`` to individual methods within a device:

.. code-block:: python
class MyDevice(Device):
@on_thread("Method1Thread")
def method1(self):
# This will run on "Method1Thread"
...
@on_thread("Method2Thread")
def method2(self):
# This will run on "Method2Thread"
...
Priority and Overriding
^^^^^^^^^^^^^^^^^^^^^^^

When both a class and a method have ``@on_thread`` decorators, the method-level decorator takes precedence:

.. code-block:: python
@on_thread("DeviceThread")
class MyDevice(Device):
def method1(self):
# This will run on "DeviceThread"
...
@on_thread("SpecialThread")
def method2(self):
# This will run on "SpecialThread", overriding the class-level decorator
...
While ``@on_thread`` provides great flexibility, be mindful of potential overhead from excessive thread switching. Use it judiciously, especially for frequently called methods.


4 changes: 2 additions & 2 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ Key Features:

2. **Adaptable to Multiple Frontends**: Compatible with GUIs, scripts, networked automated labs, and AI-integrated microscopy

3. **Powerful Threading Capabilities**: Utilities for parallelization, asynchronous execution, and complex, multi-device workflows.
3. :ref:`Powerful Threading Capabilities <threading>`: Utilities for parallelization, asynchronous execution, and complex, multi-device workflows.

4. **Modality Agnostic**: Adaptable to diverse microscopy techniques thanks to general purpose design.
4. **Modality Agnosticism**: Adaptable to diverse microscopy techniques thanks to general purpose design.

5. **Modular, Reusable Device Instructions**: Building blocks that can be combined to create complex workflows, in order to promote code reuse and simplify experiment design

Expand Down
1 change: 0 additions & 1 deletion docs/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,3 @@ Usage
usage/installation
usage/backends
usage/key_features
usage/threading
1 change: 1 addition & 0 deletions src/exengine/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@
from ._version import __version__, version_info

from .kernel.executor import ExecutionEngine
from .kernel.threading_decorator import on_thread
5 changes: 5 additions & 0 deletions src/exengine/integration_tests/test_imports.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ def test_mm_imports():
except ImportError as e:
pytest.fail(f"Import failed for MicroManagerDevice: {e}")

def test_onthread_import():
try:
from exengine import on_thread
except ImportError as e:
pytest.fail(f"Import failed for MicroManagerDevice: {e}")

def test_ndstorage_imports():
try:
Expand Down
Loading

0 comments on commit 7c66389

Please sign in to comment.