如果线程运行webbrowser.open(),则无法使用Ctrl-C退出Python脚本

我正在使用用于
Python的Bottle Web应用程序框架(pip安装瓶),并希望运行一个只能从本地计算机访问的Web应用程序(它本质上是一个使用GUI浏览器的桌面应用程序).要启动瓶子web应用程序,我必须调用bottle.run(),但只要脚本正在运行,这就会阻塞.按Ctrl-C停止它.

但是,我也希望这个应用程序通过调用webbrowser.open()打开到localhost的Web浏览器.问题是,我不能先调用webbrowser.open()因为web应用程序不会运行,但如果我先调用bottle.run(),只要Web应用程序运行,它就不会返回无法继续调用webbrowser.open().

我的解决方案是将调用webbrowser.open()放在一个线程中:

import bottle
import threading
import webbrowser
import time

class BrowserOpener(threading.Thread):
  def run(self):
    time.sleep(1) # waiting 1 sec is a hack, but it works
    webbrowser.open('http://localhost:8042')
    print('Browser opened')

@bottle.route('/')
def index():
  return 'hello world!'

BrowserOpener().start()
bottle.run(host='localhost', port=8042)

这个问题现在按下终端中的Ctrl-C似乎不起作用,所以除了完全关闭终端之外我无法停止Web应用程序.我不知道为什么会这样:’浏览器打开’被打印到屏幕上,所以我知道webbrowser.open()正在返回.

我在Windows 7上.

我已经尝试过设置self._running = False的how to terminate a thread which calls the webbrowser in python解决方案,但这并没有改变任何东西.在线程之外也没有我可以调用join()的地方.

即使我摆脱了单独的线程并使用os.system(‘python openbrowser.py’)来运行等待一秒的脚本并打开webbrowser,这仍然会阻止Ctrl-C工作.

我还尝试使用threading.Timer(1,webbrowser.open,[‘http:// localhost:8042’]).start()启动浏览器,但这仍然阻止了Ctrl-C的工作.

有没有我没见过的解决方案?

最佳答案 这个答案有两个直接警告:

>可能有一种方法可以完成您想要的更接近原始设计的方法.如果你不想偏离原来的想法,也许另一个回答者可以提供更好的解决方案.
>此解决方案尚未在Windows上进行测试,因此可能遇到与无法识别Ctrl-C信号相同或类似的问题.不幸的是,我没有一台带有Python解释器的Windows机器,可以先试用它.

除此之外:

您可能会发现通过将服务器放在单独的线程中然后通过一些简单的信号从主(非阻塞)线程控制它,您可以更轻松地进行操作.
我在下面创建了一个玩具示例,以展示我的意思.您可能更喜欢将类放在文件中,然后只需导入它并将该类的新实例实例化到其他脚本中.

import ctypes
import threading
import webbrowser
import bottle


class SimpleExampleApp():
    def __init__(self):
        self.app = bottle.Bottle()

        #define all of the routes for your app inside the init method
        @self.app.get('/')
        def index():
            return 'It works!'

        @self.app.get('/other_route')
        def alternative():
            return 'This Works Too'

    def run(self):
        self.start_server_thread()
        #depending upon how much configuration you are doing
        #when you start the server you may need to add a brief
        #delay before opening the browser to make sure that it
        #is ready to receive the initial request
        webbrowser.open('http://localhost:8042')

    def start_server_thread(self):
        self.server_thread = threading.Thread(
            target = self.app.run, 
            kwargs = {'host': 'localhost', 'port': 8042}
        )
        self.server_thread.start()

    def stop_server_thread(self):
        stop = ctypes.pythonapi.PyThreadState_SetAsyncExc(
            ctypes.c_long(self.server_thread.get_ident()), 
            ctypes.py_object(KeyboardInterrupt)
        )
        #adding a print statement for debugging purposes since I
        #do not know how well this will work on Windows platform
        print('Return value of stop was {0}'.format(stop))
        self.server_thread.join()


my_app = SimpleExampleApp()
my_app.run()

#when you're ready to stop the app, simply call
#my_app.stop_server_thread()

出于实际应用的目的,您可能需要对此进行相当大的修改,但它应该可以帮助您入门.
祝好运!

点赞