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

Al *_*art 7 python multithreading bottle

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

但是,我还希望此应用程序通过调用webbrowser.open(). 问题是,我不能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)
Run Code Online (Sandbox Code Playgroud)

现在的问题是在终端中按 Ctrl-C 似乎不起作用,所以除了完全关闭终端之外,我无法停止网络应用程序。我不确定这是为什么:'Browser opened'被打印到屏幕上,所以我知道webbrowser.open()正在返回。

我在 Windows 7 上。

我已经尝试了如何终止在 python设置中调用 webbrowser 的线程的解决方案,self._running = False但这不会改变任何东西。线程之外也没有我可以调用的地方join()地方。

即使我摆脱了单独的线程并用于os.system('python openbrowser.py')运行等待一秒钟并打开网络浏览器的脚本,这仍然会阻止 Ctrl-C 工作。

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

有没有我没有看到的解决方案?

use*_*605 1

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

  1. 可能有一种方法可以实现您想要的更接近您最初的设计。如果您不想偏离原来的想法,也许另一个回答者可以提供更好的解决方案。
  2. 此解决方案尚未在 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()
Run Code Online (Sandbox Code Playgroud)

为了您的实际应用程序的目的,您可能需要对其进行相当大的修改,但它应该可以帮助您入门。祝你好运!