2016-11-13 49 views
10

Mam kilka ekranów. Jeden z nich (DataScreen) zawiera 8 etykiet, które powinny pokazywać aktualne wartości czujników. Czujniki są odczytywane przez oddzielny proces (który rozpoczyna się z MainScreen). Sam proces jest instancją multiprocessing.Process.Aktualizuj etykiety w osobnym module roboczym (instancja procesu)

mogę uzyskać odniesienie do etykiet przez sensor_labels = self.manager.get_screen('data').l

Jednak nie mogę dowiedzieć się, jak zmienić je w podproces. Mogę zmienić je z dowolnej funkcji, która nie jest oddzielny proces, po prostu robi coś takiego:

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

Niestety, wydaje się być trudniejsze do zabicia odniesienie do sensor_labels do pracownika. Jeśli przekazuję je jako argument, oba procesy (kivy i robotnik) wydają się dzielić tym samym obiektem (identyfikator jest taki sam). Jednak jeśli zmienię label.text = 'New Text' nic się nie zmieni w Kivy.

Dlaczego identyfikator obu obiektów jest taki sam, ale tekst nie jest zmieniony? Jak mogę udostępnić obiekt etykiety Kivy innemu procesowi?

Oto moja praca minimalne Przykład

#! /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() 

a plik .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' 
+1

Wiele procesów nie udostępnia danych (domyślnie). Zobacz np. [to pytanie] (https://stackoverflow.com/questions/2080660/python-multiprocessing-and-a-shared-counter). – syntonym

+0

Ale dlaczego identyfikator jest taki sam? Spojrzałem na wieloprocesorowy.manager, ale o ile zrozumiałem, można go używać tylko z niektórymi typami obiektów. Jakieś pomysły na przekazanie pracownikowi obiektu Kivy? – Moritz

+0

Myślę, że interpreter python jest "duplikowane", a więc internals takich jak id są takie same, ale nie wiem, internals, że dobrze. Czy możesz zamiast tego używać wątków? – syntonym

Odpowiedz

2

Wygląda na to, że możesz źle zrozumieć, jak działa proces wieloprocesowy.

Po uruchomieniu nowego Process z biblioteką wieloprocesową tworzy nowy proces i ogłasza cały kod potrzebny do uruchomienia funkcji docelowej. Wszelkie aktualizacje wprowadzone do etykiet są wykonywane w procesie roboczym i NIE uwzględniają się w procesie interfejsu użytkownika.

Aby obejść ten problem, należy użyć jednej z tych metod w celu wymiany danych między procesami pracownika i interfejsu użytkownika: https://docs.python.org/2/library/multiprocessing.html#exchanging-objects-between-processes. Skoro masz już kolejkę można zrobić coś takiego:

Umieść swoje read_sensors do worker.py przepuszczanie tx i rx Kolejka gdzie tx służy do wysyłania do interfejsu użytkownika i rx służy do odczytu z UI.

#! /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) 

Następnie w aplikacji używać Clock wezwać obsługi aktualizacji sprawdzić tx kolejce do aktualizacji okresowo. Podczas zamykania interfejs użytkownika może nakazać pracownikowi zatrzymanie się, umieszczając komunikat w kolejce 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() 

Również klasa multiprocessing.Queue już jest „proces” bezpieczne, nie trzeba korzystać z blokady wokół niego. Jeśli masz oddzielny proces dla każdego czujnika, możesz użyć tego samego pomysłu, po prostu więcej kolejek.

+0

Prostsze niż myślałem. – Moritz

+0

Myślałem, że id jest czymś wyjątkowym, wskazując na samą pamięć, ale wydaje się wskazywać na pewien zakres pamięci. – Moritz

1

Kivy nie przewiduje IPC, a elementy GUI powinny być aktualizowane tylko w głównym wątku. Aby wdrożyć IPC, możesz użyć OSC, aby to ułatwić, zobacz this. Jeśli przeniesiesz odczytywanie czujników wewnątrz wątków, przeczytaj this i this, jeśli jeszcze tego nie zrobiłeś.