后台进程锁定GUI Python

时间:2014-02-19 17:56:49

标签: python python-2.7 multiprocessing

我有一个后台Process(使用来自Process的{​​{1}})将对象推送到我的GUI,但是这个后台进程会一直锁定GUI并且推送的更改永远不会被显示。对象被放入我的队列中,但我的GUI中的更新方法没有被定期调用。我能做些什么使GUI更新更频繁?我的GUI是用Tkinter编写的。

我的后台进程中有一个无限循环,因为我总是需要继续读取USB端口以获取更多数据,所以基本上我的代码看起来像这样:

TracerAccess.py

multiprocessing

ui.py

import usb
from types import *
import sys
from multiprocessing import Process, Queue
import time


__idVendor__ = 0xFFFF
__idProduct__ = 0xFFFF

END_POINT = 0x82

def __printHEXList__(list):
    print ' '.join('%02x' % b for b in list)

def checkDeviceConnected():
    dev = usb.core.find(idVendor=__idVendor__, idProduct=__idProduct__)
    if dev is None:
        return False
    else:
        return True

class LowLevelAccess():
    def __init__(self):
        self.rawIn = []
        self.tracer = usb.core.find(idVendor=__idVendor__, idProduct=__idProduct__)
        if self.tracer is None:
            raise ValueError("Device not connected")
        self.tracer.set_configuration()

    def readUSB(self):
        """
        This method reads the USB data from the simtracer.
        """
        try:
            tmp = self.tracer.read(END_POINT, 10000,None, 100000).tolist()
            while(self.checkForEmptyData(tmp)):
                tmp = self.tracer.read(END_POINT, 10000,None, 100000).tolist()
            self.rawIn = tmp
        except:
            time.sleep(1)
            self.readUSB()

    def checkForEmptyData(self, raw):
        if(len(raw) == 10 or raw[10] is 0x60 or len(raw) == 11):
            return True
        else:
            return False

class DataAbstraction:
    def __init__(self, queue):
        self.queue = queue
        self.lowLevel = LowLevelAccess()
    def readInput(self):
        while True:
            self.lowLevel.readUSB()
            raw = self.lowLevel.rawIn
            self.queue.put(raw)

Main.py

from Tkinter import *
import time
import TracerAccess as io
from multiprocessing import Process, Queue
from Queue import Empty
from math import ceil

def findNumberOfLines(message):
    lines = message.split("\n")
    return len(lines)


class Application(Frame):
    def addTextToRaw(self, text, changeColour=False, numberOfLines=0):
        self.rawText.config(state=NORMAL)
        if changeColour is True:
            self.rawText.insert(END,text, 'oddLine')
        else:
            self.rawText.insert(END,text)
        self.rawText.config(state=DISABLED)

    def updateRaw(self, text):
        if(self.numberOfData() % 2 is not 0):
            self.addTextToRaw(text, True)
        else:
            self.addTextToRaw(text)
    def startTrace(self):
        self.dataAbstraction = io.DataAbstraction(self.queue)
        self.splitProc = Process(target=self.dataAbstraction.readInput())
        self.stopButton.config(state="normal")
        self.startButton.config(state="disabled")
        self.splitProc.start()

    def pollQueue(self):
        try:
            data = self.queue.get(0)
            self.dataReturned.append(data)
            self.updateRaw(str(data).upper())
            self.rawText.tag_config("oddLine", background="#F3F6FA")
        except Empty:
            pass
        finally:
            try:
                if(self.splitProc.is_alive() is False):
                    self.stopButton.config(state="disabled")
                    self.startButton.config(state="normal")
            except AttributeError:
                pass
            self.master.after(10, self.pollQueue)

    def stopTrace(self):
        self.splitProc.join()
        self.stopButton.config(state="disabled")
        self.startButton.config(state="normal")

    def createWidgets(self):
        self.startButton = Button(self)
        self.startButton["text"] = "Start"
        self.startButton["command"] = self.startTrace
        self.startButton.grid(row = 0, column=0)

        self.stopButton = Button(self)
        self.stopButton["text"] = "Stop"
        self.stopButton["command"] = self.stopTrace
        self.stopButton.config(state="disabled")
        self.stopButton.grid(row = 0, column=1)

        self.rawText = Text(self, state=DISABLED, width=82)
        self.rawText.grid(row=1, columnspan=4)


    def __init__(self, master):
        Frame.__init__(self, master)
        self.queue = Queue()
        self.master.after(10, self.pollQueue)
        self.pack()
        self.dataReturned = []
        self.createWidgets()

    def numberOfData(self):
        return len(self.dataReturned)

因此后台线程永远不会完成,但是当我结束进程时,UI会在关闭之前开始显示。问题可能出现了,因为我设计了TracerAccess.py模块,因为我在移动直接形式的java之后开发了这个,而对于python几乎没有设计经验。

1 个答案:

答案 0 :(得分:4)

multiprocess.Process内部实际上是fork(),它有效地复制了您的流程。您可以将其可视化为:

                  / ["background" process] -------------\
[main process] --+                                       +-- [main process]
                  \ [main process continued] -----------/

p.join()尝试将两个进程“加入”一个进程。这实际上意味着:等到后台进程完成。这是.join()函数的实际(完整)代码:

def join(self, timeout=None):
    '''
    Wait until child process terminates
    '''
    assert self._parent_pid == os.getpid(), 'can only join a child process'
    assert self._popen is not None, 'can only join a started process'
    res = self._popen.wait(timeout)
    if res is not None:
        _current_process._children.discard(self)

请注意self._popen.wait的调用方式。

这显然不是你想要的。

在TKinter的上下文中,您可能需要使用tk事件循环,例如像这样(Python 3,但该概念也适用于Python 2)

from multiprocessing import Process, Queue
import time, tkinter, queue, random, sys

class Test:
    def __init__(self, root):
        self.root = root
        txt = tkinter.Text(root)
        txt.pack()

        self.q = Queue()

        p = Process(target=self.bg)
        p.start()

        self.checkqueue()
        print('__init__ done', end=' ')

    def bg(self):
        print('Starting bg loop', end=' ')
        n = 42
        while True:
            # Burn some CPU cycles
            (int(random.random() * 199999)) ** (int(random.random() * 1999999))
            n += 1
            self.q.put(n)
            print('bg task finished', end=' ')

    def checkqueue(self):
        try:
            print(self.q.get_nowait(), end=' ')
        except queue.Empty:
            print('Queue empty', end=' ')

        sys.stdout.flush()

        # Run myself again after 1 second
        self.root.after(1000, self.checkqueue)


root = tkinter.Tk()
Test(root)
root.mainloop()

你没有调用.join(),而是使用.after()方法,它调度一个函数在n微秒之后运行(如果你曾经使用过Javascript,那就认为setTimeout())读取队列。

根据bg()功能的实际内容,您可能根本不需要multiprocesing,只需安排.after() 的功能即可

另见: http://tkinter.unpythonic.net/wiki/UsingTheEventLoop