更新单独工作人员中的标签(流程实例)

时间:2016-11-13 22:37:56

标签: python multiprocessing kivy

我有几个屏幕。其中一个(DataScreen)包含8个标签,应显示当前传感器值。传感器由单独的进程读取(从MainScreen启动)。该过程本身是multiprocessing.Process的实例。

我可以通过sensor_labels = self.manager.get_screen('data').l

获得对标签的引用

但是,我无法弄清楚如何在子进程中更改它们。我可以从任何不是单独过程的函数中更改它们,只需执行以下操作:

for item in sensor_labels:
    item.text = 'Update'

不幸的是,将sensor_labels的引用传递给工作者似乎更加困难。如果我将它们作为参数传递,则两个进程(kivy和worker)似乎共享同一个对象(id是相同的)。但是,如果我改变label.text = 'New Text' Kivy没有任何变化。

为什么两个对象的id都相同,但文本没有改变? 如何与另一个进程共享Kivy标签对象?

这是我工作的最小例子

#! /usr/bin/env python
""" Reading sensor data
"""
from kivy.config import Config
Config.set('kivy', 'keyboard_mode', 'multi')
from kivy.app import App
from kivy.lang import Builder
from kivy.properties import StringProperty, ObjectProperty, NumericProperty
from kivy.uix.label import Label
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.stacklayout import StackLayout
from multiprocessing import Process, Queue, Array
# all other modules
import time
import numpy as np
from multiprocessing import Lock
class MainScreen(Screen):

    def __init__(self, **kwargs):
        super(MainScreen, self).__init__(**kwargs)
        self.n_probes = 8

    @staticmethod
    def read_sensors(qu_rx, sensor_labels, lock):
        while True:
            if not qu_rx.empty():
                message = qu_rx.get()
                if message == 'STOP':
                    print('Worker: received poison pill')
                    break

            data = np.random.random()
            print('ID of labels in worker: {}'.format(id(sensor_labels)))

            print('Text of labels in worker:')
            lock.acquire()
            for label in sensor_labels:
                label.text = '{0:2f}'.format(data)
                print(label.text)
            lock.release()
            time.sleep(5)

    def run_worker(self, *args, **kwargs):
        self.qu_tx_worker = Queue()
        lock = Lock()
        # this is a reference to the labels in the DataScreen class
        self.sensor_labels = self.manager.get_screen('data').l
        self.worker = Process(target=self.read_sensors,
                              args=(self.qu_tx_worker, self.sensor_labels, lock))
        self.worker.daemon = True

        self.worker.start()

    def stop_worker(self, *args, **kwargs):
        self.qu_tx_worker.put('STOP')
        print('Send poison pill')
        self.worker.join()
        print('All worker dead')

        print('ID of labels in Kivy: {}'.format(id(self.sensor_labels)))
        print('Label text in Kivy:')
        for label in self.sensor_labels:
            print(label.text)


class DataScreen(Screen):

    def __init__(self, **kwargs):
        layout = StackLayout()
        super(DataScreen, self).__init__(**kwargs)
        self.n_probes = 8
        self.label_text = []
        for i in range(self.n_probes):
            self.label_text.append(StringProperty())
            self.label_text[i] = str(i)
        self.l = []
        for i in range(self.n_probes):
            self.l.append(Label(id='l_{}'.format(i),
                          text='Start {}'.format(i),
                          font_size='60sp',
                          height=20,
                          width=20,
                          size_hint=(0.5, 0.2)))
            self.ids.stack.add_widget(self.l[i])

    def change_text(self):
            for item in self.l:
                item.text = 'Update'


Builder.load_file('phapp.kv')

class MyApp(App):
    """
    The settings App is the main app of the pHBot application.
    It is initiated by kivy and contains the functions defining the main interface.
    """

    def build(self):
        """
        This function initializes the app interface and has to be called "build(self)".
        It returns the user interface defined by the Builder.
        """

        sm = ScreenManager()
        sm.add_widget(MainScreen())
        sm.add_widget(DataScreen())
        # returns the user interface defined by the Builder
        return sm

if __name__ == '__main__':
    MyApp().run()

.kv文件:

<MainScreen>:
    name: 'main'
    BoxLayout:
        orientation: 'vertical'
        Button:
            text: 'Start Application'
            font_size: 40
            on_release: root.run_worker()
        Button:
            text: 'Stop Application'
            font_size: 40
            on_release: root.stop_worker()
        Button:
            text: 'Go to data'
            font_size: 40
            on_release: app.root.current = 'data'
        Button:
            text: 'Exit'
            font_size: 40
            on_release: app.stop()

<DataScreen>:
    name: 'data'
    StackLayout:
        id: stack
        orientation: 'lr-tb'
    BoxLayout:
        Button:
            size_hint: (0.5, 0.1)
            text: 'Update'
            font_size: 30
            on_release: root.change_text()
        Button:
            size_hint: (0.5, 0.1)
            text: 'Back to main menu'
            font_size: 30
            on_release: app.root.current = 'main'

2 个答案:

答案 0 :(得分:2)

看起来您可能会误解多处理的工作原理。

当您使用多处理库启动新的Process时,它会创建一个新进程并挑选运行目标函数所需的所有代码。您对传递的标签所做的任何更新都发生在工作进程中,不会反映在UI进程中。

要解决此问题,您必须使用其中一种方法在工作进程和UI进程之间交换数据:https://docs.python.org/2/library/multiprocessing.html#exchanging-objects-between-processes。由于你已经有了一个队列,你可以这样做:

read_sensors放入worker.py通过txrx队列,其中tx用于发送到用户界面,rx是用来从用户界面阅读。

#! /usr/bin/env python
""" Reading sensor data
"""
import time
import numpy as np

def read_sensors(rx,tx, n):
    while True:
        if not rx.empty():
            message = rx.get()
            if message == 'STOP':
                print('Worker: received poison pill')
                break

        #: Sensor value for each label
        data = [np.random.random() for i in range(n)]

        #: Formatted data
        new_labels = ['{0:2f}'.format(x) for x in data]
        print('Text of labels in worker: {}'.format(new_labels))
        #lock.acquire() # Queue is already safe, no need to lock

        #: Put the formatted label in the tx queue
        tx.put(new_labels)

        # lock.release() # Queue is already safe, no need to unlock
        time.sleep(5)

然后在您的应用中使用Clock调用更新处理程序,定期检查tx队列的更新。退出时,UI可以通过在rx队列中放置消息来告诉工作人员停止。

#! /usr/bin/env python
""" Reading sensor data
"""
from kivy.config import Config
from kivy.clock import Clock
Config.set('kivy', 'keyboard_mode', 'multi')
from kivy.app import App
from kivy.lang import Builder
from kivy.properties import StringProperty, ObjectProperty, NumericProperty
from kivy.uix.label import Label
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.stacklayout import StackLayout
from multiprocessing import Process, Queue

#: Separate worker file so a separate app is not opened
import worker

class MainScreen(Screen):

    def __init__(self, **kwargs):
        super(MainScreen, self).__init__(**kwargs)
        self.n_probes = 8

        #: Hold the update event
        self._event = None

    def read_worker(self,dt):
        """ Read the data from the worker process queue"""
        #: Get the data from the worker (if given) without blocking
        if self.tx.empty():
            return # No data, try again later

        #: The worker put data in the queue, update the labels
        new_labels = self.tx.get()
        for label,text in zip(self.sensor_labels,new_labels):
            label.text = text

    def run_worker(self, *args, **kwargs):
        self.rx = Queue() #: Queue to send data to worker process 
        self.tx = Queue() #: Queue to recv from worker process
        self.sensor_labels = self.manager.get_screen('data').l
        self.worker = Process(target=worker.read_sensors,
                              args=(self.rx,self.tx,self.n_probes))
        self.worker.daemon = True
        self.worker.start()

        # Check the tx queue for updates every 0.5 seconds
        self._event = Clock.schedule_interval(self.read_worker, 0.5)

    def stop_worker(self, *args, **kwargs):
        self.rx.put('STOP')
        print('Send poison pill')
        self.worker.join()
        print('All worker dead')

        #: Stop update loop
        if self._event:
            self._event.cancel()

        print('ID of labels in Kivy: {}'.format(id(self.sensor_labels)))
        print('Label text in Kivy:')
        for label in self.sensor_labels:
            print(label.text)


class DataScreen(Screen):

    def __init__(self, **kwargs):
        layout = StackLayout()
        super(DataScreen, self).__init__(**kwargs)
        self.n_probes = 8
        self.label_text = []
        for i in range(self.n_probes):
            self.label_text.append(StringProperty())
            self.label_text[i] = str(i)
        self.l = []
        for i in range(self.n_probes):
            self.l.append(Label(id='l_{}'.format(i),
                          text='Start {}'.format(i),
                          font_size='60sp',
                          height=20,
                          width=20,
                          size_hint=(0.5, 0.2)))
            self.ids.stack.add_widget(self.l[i])

    def change_text(self):
            for item in self.l:
                item.text = 'Update'


Builder.load_file('phapp.kv')

class MyApp(App):
    """
    The settings App is the main app of the pHBot application.
    It is initiated by kivy and contains the functions defining the main interface.
    """

    def build(self):
        """
        This function initializes the app interface and has to be called "build(self)".
        It returns the user interface defined by the Builder.
        """

        sm = ScreenManager()
        sm.add_widget(MainScreen())
        sm.add_widget(DataScreen())
        # returns the user interface defined by the Builder
        return sm

if __name__ == '__main__':
    MyApp().run()

此外,multiprocessing.Queue课程已经过程&#39;安全,你不需要在它周围使用锁。如果每个传感器都有一个单独的过程,则可以使用相同的想法,只需更多队列。

答案 1 :(得分:1)

Kivy不提供IPC,GUI元素只应在主线程中更新。要实现IPC,您可以使用OSC来实现这一点,请参阅this。如果您移动读取线程内的传感器,请阅读thisthis,如果您还没有这样做。