使用 Pyinstaller 的 ModalView 间歇性失败
Intermittent Failure of ModalView with Pyinstaller
我有一个 App
,它使用 Clock.schedule_once()
从 Thread
打开一个 ModalView
弹出窗口,并使用 Queue
等待弹出窗口关闭。大约 90% 的时间都可以正常工作。但有时,线程正在等待关闭而没有显示弹出窗口。此失败仅在 运行 由 Pyinstaller
生成的 exe
时发生,并且仅在第一次尝试时发生(即第一次单击 "Run Test" 按钮)。如果第一次尝试成功,那么接下来的所有尝试也会成功。
我正在使用:
- 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)
原来问题是主线程在Kivy Clock
中挂起(通过做一些跟踪验证)并且没有真正执行self.pop.open
方法。这似乎只发生在 Windows 上的 Pyinstaller
生成的 EXE 中。我在 Ubuntu 上用 Pyinstaller
测试了相同的代码,没有失败。 fix/workaround 是通过在 `main.py' 的开头插入以下代码来使用不同的 kivy Clock
:
from kivy.config import Config
Config.set('kivy', 'kivy_clock', 'interrupt')
这使得 kivy
使用不同的 Clock
并消除了问题
我有一个 App
,它使用 Clock.schedule_once()
从 Thread
打开一个 ModalView
弹出窗口,并使用 Queue
等待弹出窗口关闭。大约 90% 的时间都可以正常工作。但有时,线程正在等待关闭而没有显示弹出窗口。此失败仅在 运行 由 Pyinstaller
生成的 exe
时发生,并且仅在第一次尝试时发生(即第一次单击 "Run Test" 按钮)。如果第一次尝试成功,那么接下来的所有尝试也会成功。
我正在使用:
- 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)
原来问题是主线程在Kivy Clock
中挂起(通过做一些跟踪验证)并且没有真正执行self.pop.open
方法。这似乎只发生在 Windows 上的 Pyinstaller
生成的 EXE 中。我在 Ubuntu 上用 Pyinstaller
测试了相同的代码,没有失败。 fix/workaround 是通过在 `main.py' 的开头插入以下代码来使用不同的 kivy Clock
:
from kivy.config import Config
Config.set('kivy', 'kivy_clock', 'interrupt')
这使得 kivy
使用不同的 Clock
并消除了问题