【问题标题】:Intermittent Failure of ModalView with Pyinstaller使用 Pyinstaller 的 ModalView 间歇性失败
【发布时间】:2018-04-25 18:15:30
【问题描述】:

我有一个App,它使用Clock.schedule_once()Thread 打开一个ModalView 弹出窗口,并使用Queue 等待弹出窗口关闭。这在大约 90% 的时间里都能正常工作。但有时,线程正在等待关闭而不显示弹出窗口。此故障仅在运行Pyinstaller 生成的exe 时发生,并且仅在第一次尝试时发生(即第一次单击“运行测试”按钮)。如果第一次尝试成功,那么接下来的所有尝试也会成功。

我正在使用:

  • Pyinstaller 版本 3.3.dev0+483c819
  • Windows 10

我正在使用 Python 3 进行开发,但 Pyinstaller 生成的代码运行 Python 2.7.14。调试输出仅显示成功和失败之间的预期差异。

这个例子是从一个更复杂的应用程序中提炼出来的。如果有人能看到问题,或者推荐一种更可靠的方式从Thread 打开ModalView,请告诉我。

main.py:

import threading

from kivy.app import App
from kivy.clock import Clock
from kivy.lang import Builder
from kivy.uix.button import Button
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.modalview import ModalView

from kivy.compat import PY2
if PY2:
    from Queue import Queue
else:
    from queue import Queue


class TestLayout(FloatLayout):
    def __init__(self):
        super(TestLayout, self).__init__()

    def do_test(self, *arg):
        self.th = AThread()
        self.th.start()


class MyPopup(object):
    def __init__(self, callback):
        self.buttonCallback = callback
        dismiss_button = Button(text='Dismiss')
        dismiss_button.bind(on_press=self.butt)
        self.popup = ModalView(size_hint=(.5, .5), auto_dismiss=False)
        self.popup.add_widget(dismiss_button)

    def butt(self, *args):
        if self.popup is not None:
            self.popup.dismiss()
        if self.buttonCallback is not None:
            self.buttonCallback()

    def open(self, *args):
        self.popup.open()


class AThread(threading.Thread):
    def __init__(self):
        super(AThread, self).__init__()
        self.daemon = True
        self.pop = None
        self.queue = None

    def run(self):
        print('running')
        self.queue = Queue()
        self.pop = MyPopup(lambda: self.queue.put(None, False))
        Clock.schedule_once(self.pop.open)    # This should open the popup, but occasionally it does not
        print('waiting')
        self.queue.get(True)
        print('done waiting')
        self.queue = None


root = Builder.load_string( '''
TestLayout:
    Button:
        text: 'Run Test'
        on_press: root.do_test()
''')


class testApp(App):
    def build(self):
        return root


testApp().run()

main.spec:

# -*- mode: python -*-

from kivy.deps import sdl2, glew

block_cipher = None


a = Analysis(['main.py'],
             pathex=['C:\\Users\\John\\PyCharmProjects\\popupbug'],
             binaries=[],
             datas=[],
             hookspath=[],
             runtime_hooks=[],
             excludes=[],
             win_no_prefer_redirects=False,
             win_private_assemblies=False,
             cipher=block_cipher)
pyz = PYZ(a.pure, a.zipped_data,
             cipher=block_cipher)
exe = EXE(pyz,
          a.scripts,
          a.binaries,
          a.zipfiles,
          a.datas,
          *[Tree(p) for p in (sdl2.dep_bins + glew.dep_bins)],
          name='Test',
          debug=True,
          strip=False,
          upx=True,
          runtime_tmpdir=None,
          console=True)

【问题讨论】:

  • 你试过Clock.schedule_once(self.popup.open, -1),即在下一帧之前调用吗?
  • 是的,我有。我还尝试使用 @mainthread 装饰器而不是 Clock.schedule_once。都产生了相同的结果。

标签: python kivy pyinstaller


【解决方案1】:

事实证明,问题在于主线程挂在 Kivy Clock 中(通过进行一些跟踪验证)并没有实际执行 self.pop.open 方法。这似乎只发生在Pyinstaller 在 Windows 上生成的 EXE 上。我在 Ubuntu 上使用 Pyinstaller 测试了相同的代码,没有失败。修复/解决方法是通过在 `main.py' 的开头插入以下代码来使用不同的 kivy Clock

from kivy.config import Config
Config.set('kivy', 'kivy_clock', 'interrupt')

这使kivy 使用不同的Clock 并消除了问题

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-04-18
    • 2011-10-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-04-19
    • 1970-01-01
    • 2023-04-02
    相关资源
    最近更新 更多