Create a task and a 'service'
Last updated on 2026-02-26 | Edit this page
Overview
Questions
- What are tasks and what can they do?
- Can I run a task as a (background) ‘service’ process?
Objectives
- Learn how to create tasks with (customized) task runners
- Learn how to make a background ‘service’ process with
qmi_proc
Example: QMI service process to control amplitude of sine wave
Next, we want to demonstrate QMI tasks and how they can be used in setting up services, i.e. tasks running as background processes, on your PC. As an example we will create a task which will constrain the sine wave amplitude to be around 10 (of arbitrary units) and run this task as a background process - a “service” in QMI slang.
Demo task
To demonstrate a custom task, we need to create one. Make a new
Python module inside the module path for your project. If you don’t have
a module path yet, just create a file demo_task.py in the
current directory:
PYTHON
from dataclasses import dataclass
import logging
import qmi
from qmi.core.rpc import rpc_method
from qmi.core.task import QMI_Task, QMI_TaskRunner
# Global variable holding the logger for this module.
_logger = logging.getLogger(__name__)
@dataclass
class DemoLoopTaskSettings:
sample: float | None
amplitude: float
class CustomRpcControlTaskRunner(QMI_TaskRunner):
@rpc_method
def set_amplitude(self, amplitude: float):
settings = self.get_settings()
settings.amplitude = amplitude
self.set_settings(settings)
class DemoRpcControlTask(QMI_Task):
def __init__(self, task_runner, name):
super().__init__(task_runner, name)
self.settings = DemoLoopTaskSettings(amplitude=100.0, sample=None)
def run(self):
_logger.info("starting the background task")
nsg = qmi.get_instrument("proc_demo.nsg")
while not self.stop_requested():
self.update_settings()
nsg.set_amplitude(self.settings.amplitude)
self.sleep(0.01)
_logger.info("stopping the background task")
Note that we define class DemoRpcControlTask with one
special method named run(). This method contains the code
that makes up the background task. In this simple example, the task
simply loops about 100 times per second, reading settings from the sine
generator and adjusting its amplitude. The task uses the function qmi.core.task.QMI_Task.sleep()
to sleep instead of time.sleep(). The advantage of this is
that it stops waiting immediately when it is necessary to stop the
task.
Task runner script
Now we create the program starting up and running the task. Make
task_demo.py file with the following contents:
PYTHON
import logging
import time
import qmi
from qmi.utils.context_managers import start_stop
from qmi.instruments.dummy.noisy_sine_generator import NoisySineGenerator
from demo_task import DemoRpcControlTask, CustomRpcControlTaskRunner
# Global variable holding the logger for this module.
_logger = logging.getLogger(__name__)
def main():
with start_stop(qmi, "proc_demo", config_file="./qmi.conf"):
ctx = qmi.context()
with qmi.make_instrument("nsg", NoisySineGenerator) as nsg:
task = qmi.make_task("demo_task", DemoRpcControlTask, task_runner=CustomRpcControlTaskRunner)
task.start()
_logger.info("the task has been started")
try:
while task.is_running() and not ctx.shutdown_requested():
sample = nsg.get_sample()
amplitude = nsg.get_amplitude()
print(" " * int(40.0 + 0.25 * sample) + "*")
if abs(sample) > 10:
task.set_amplitude(amplitude * 0.99)
else:
task.set_amplitude(amplitude * 1.01)
time.sleep(0.01)
task.stop()
except:
task.stop()
finally:
_logger.info("the task has been stopped")
task.join()
if __name__ == "__main__":
main()
This program now takes care of creating the instrument
nsg and followingly starting up the task, using the
with context managers. The script loops about hundred times
per second, and at each iteration prints out the latest sine wave point
(*), and controls the DemoRpcControlTask’s amplitude. In
practice, we are now suppressing the sine wave by continuously checking
its sample value and setting the amplitude so that the sample value
would stay as close to 10 as possible.
Configuration file
The task needs now somewhat more complex configuration of the
qmi.conf, but nothing scary, I promise. The new
configuration will now have a new entry for the background process
proc_demo:
{
# Log level for messages to the console.
"logging": {
"console_loglevel": "INFO"
# "logfile": "log.log"
},
# Directory to write main log file.
"log_dir": "~/qmi_course/log",
"contexts": {
# Testing remote instrument access.
"instr_server": {
"host": "127.0.0.1",
"tcp_server_port": 40001
},
# Testing process management.
"proc_demo": {
"host": "127.0.0.1",
"tcp_server_port": 40002,
"enabled": true,
"program_module": "task_demo"
}
},
"process_management": {
"output_dir": "~/qmi_course/log"
}
}
The ‘”enabled”: true’ parameter makes it possible to start the context via QMI process management, and ‘”program_module”: “task_demo”’ line tells the QMI to start a module named ‘task_demo.py’ for this process. We’ll start it in a qubit.
Running the task as a service with qmi_proc
We can now start the service using qmi_proc program.
qmi_proc is a command-line executable created when
installing QMI, see also documentation about managing
background processes. It can be used to start, stop and inquire
status of services. To start the “proc_demo” service, type the following
(the “–config ./qmi.conf” is not necessary if the qmi.conf
is in the default path).
qmi_proc start proc_demo --config ./qmi.conf
Leave the program now to run a few seconds and then stop it:
qmi_proc stop proc_demo --config ./qmi.conf
The use of the qmi_proc creates extra output files for
services. One was now created also for “proc_demo” service. You can find
it in the default location with name “proc_demo_
Challenge
You might want to try to manually disturb the service to confirm that
the suppression of the sine wave really works. For that, re-start the
service again. Now, in another terminal, start up QMI (using the same
qmi.conf file) and connect to the task’s QMI context and
use QMI’s get_task command to get a proxy for the task.
From this proxy, use the custom RPC method set_amplitude to
crank up the amplitude. Then see the task’s log file what happened.
BONUS QUESTION:: You have to use floating point values for the
set_amplitude command. An integer value (like 1000) will
crash the task. Why is that?
There are plenty of other things going on on this example, like the
use of a custom QMI_TaskRunner with an RPC method added
into the runner. We also make use of file-specific loggers for logging.
For more information about QMI tasks see the tutorial,
and about logging the Design
Overview in documentation. There are also other examples of tasks in
the examples
folder of the QMI repository.
Further, the amplitude control through the task settings actually utilizes the QMI’s signalling feature. For more details on this, you can read into signalling and look at API of qmi.core.task in the documentation.
- Also QMI tasks need to be defined in
qmi.conf. To enable running the task a a service, parameters “enabled” and “program_module” need to be defined. - A task consists of a
QMI_Taskclass and aQMI_TaskRunnerclass. The latter can be customized to include RPC methods in tasks. - Task is started with
start(), stopped withstop()and after stopping, the task thread should be “joined” withjoin()for properly exiting the thread. -
qmi_procis an executable created while installing QMI. It can be used to start, stop and checking status of (local) QMI tasks running as background processes.