为什么后台任务会阻塞SimpleHTTPServer的响应?

2 投票
3 回答
2215 浏览
提问于 2025-04-16 05:46

我正在写一个简单的浏览器前端,目的是能够启动一个后台任务,然后获取它的进度。我希望浏览器能收到一个响应,告诉我任务是否成功启动,然后再定期检查任务什么时候完成。但是,后台任务的存在似乎让 XMLHttpRequest 的响应没有立即发送,所以我无法报告启动进程的成功与否。看看下面这段(简化过的)代码:

import SocketServer
import SimpleHTTPServer
import multiprocessing
import time

class MyProc(multiprocessing.Process):
    def run(self):
        print 'Starting long process..'
        for i in range(100): time.sleep(1)
        print 'Done long process'

class Page(SimpleHTTPServer.SimpleHTTPRequestHandler):
    def do_GET(self):
        if self.path == '/':
            print >>self.wfile, "<html><body><a href='/run'>Run</a></body></html>"
        if self.path == '/run':
            self.proc = MyProc()
            print 'Starting..'
            self.proc.start()
            print 'After start.'
            print >>self.wfile, "Process started."

httpd = SocketServer.TCPServer(('', 8000), Page)
httpd.serve_forever()

当我运行这段代码,并访问 http://localhost:8000 时,我看到一个名为“运行”的按钮。当我点击它时,终端显示:

Starting..
After start.

但是浏览器的界面没有变化……实际上,光标在转圈。只有当我在终端按下 Ctrl-C 来中断程序时,浏览器才会更新并显示消息 进程已启动。

消息 启动后 显然是被打印出来的。因此我可以推测 do_GET 在启动进程后是返回了的。然而,浏览器直到我中断这个长时间运行的进程后才收到响应。我不得不得出结论,在 do_GET 和响应发送之间有某种东西在阻塞,这个东西是在 SimpleHTTPServer 里面。

我也尝试过使用线程和 subprocess.Popen,但遇到了类似的问题。有什么想法吗?

3 个回答

0

答案是,multiprocessing模块会创建一个完全不同的进程,这个进程有自己的输出流... 所以你的应用程序就像你写的那样运行:

  1. 你在终端窗口启动应用程序。
  2. 你在浏览器中点击运行按钮,这时会发送一个GET请求到/run。
  3. 你在终端窗口看到当前进程的输出,显示“正在启动..”。
  4. 一个新的进程被启动,叫做MyProc,它有自己的输出流和错误流。
  5. MyProc在它的输出流中打印“正在启动长时间的进程..”,但这个输出并没有去任何地方。
  6. 就在MyProc启动的那一刻,你的应用程序在输出流中打印“启动后。”,因为它没有被告知要等MyProc的任何响应。

你需要做的是实现一个队列,这个队列可以在主应用程序的进程和被分叉的进程之间来回传递信息。这里有一些关于如何做到这一点的多进程特定示例:

http://www.ibm.com/developerworks/aix/library/au-multiprocessing/

不过,那篇文章(就像IBM的大多数文章一样)有点深奥且过于复杂... 你可能想看看一个更简单的例子,关于如何使用“常规”的队列模块(它和multiprocessing中的队列几乎是一样的):

http://www.artfulcode.net/articles/multi-threading-python/

最重要的概念是如何使用队列在进程之间传递数据,以及如何使用join()来等待响应后再继续进行。

2

我用这个小代码来运行一个多线程的简单HTTP服务器。

我把这个文件保存为 ThreadedHTTPServer.py,然后像这样运行:

$ python -m /path/to/ThreadedHTTPServer PORT

这样它就会在不同的线程中运行,你可以同时下载文件,也可以正常浏览网页。

from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
from SocketServer import ThreadingMixIn
import threading
import SimpleHTTPServer
import sys

PORT = int(sys.argv[1])

Handler = SimpleHTTPServer.SimpleHTTPRequestHandler

class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
    """Handle requests in a separate thread."""

if __name__ == '__main__':
    server = ThreadedHTTPServer(('0.0.0.0', PORT), Handler)
    print 'Starting server, use <Ctrl-C> to stop'
    server.serve_forever()
3

除了Steve和我上面的评论,这里有一个对我有效的解决方案。

确定内容长度的方法有点麻烦。如果你不指定内容长度,浏览器可能会显示一个转动的光标,尽管内容已经显示出来。关闭 self.wfile 也可能有效。

from cStringIO import StringIO

class Page(SimpleHTTPServer.SimpleHTTPRequestHandler):
    def do_GET(self):
        out = StringIO()
        self.send_response(200)
        self.send_header("Content-type", "text/html")
        if self.path == '/':
            out.write("<html><body><a href='/run'>Run</a></body></html>\n")
        elif self.path == '/run':
            self.proc = MyProc()
            print 'Starting..'
            self.proc.start()
            print 'After start.'
            out.write("<html><body><h1>Process started</h1></body></html>\n")
        text = out.getvalue()
        self.send_header("Content-Length", str(len(text)))
        self.end_headers()
        self.wfile.write(text)

撰写回答