如何优雅地中断urllib2下载?

时间:2012-08-05 14:45:42

标签: python real-time urllib2

我正在使用urllib2的{​​{1}}来创建build_opener()。我使用OpenerDirector来获取慢速页面,因此它有一个很长的超时。

到目前为止,非常好。

但是,在另一个帖子中,我被告知要中止下载 - 假设用户已选择退出GUI中的程序。

有没有办法发出urllib2下载应该退出的信号?

4 个答案:

答案 0 :(得分:8)

没有干净的答案。有几个丑陋的。

最初,我在这个问题中提出了被拒绝的想法。由于很明显没有正确的答案,我决定将各种次优选择作为列表答案发布。其中一些受到评论的启发,谢谢。

图书馆支持

理想的解决方案是OpenerDirector提供取消操作员。

没有。图书馆作者注意到:如果你提供长时间的慢速操作,你需要提供一种方法来取消它们,如果人们要在真实世界的应用程序中使用它们。

减少超时

作为其他人的一般解决方案,这可能有用。通过较小的超时,它可以更好地响应环境的变化。但是,如果它们在超时时间内没有完全完成,它也会导致下载失败,所以这是一个权衡。在我看来,这是站不住脚的。

以块的形式阅读下载内容。

同样,作为一般解决方案,这可能有效。如果下载包含非常大的文件,您可以read them in small chunks,并在读取块后中止。

不幸的是,如果(在我的情况下)延迟是在接收第一个字节而不是文件的大小,这将无济于事。

杀死整个帖子。

虽然有一些侵略性技术可以杀死线程,但取决于操作系统,they are not recommended。特别是,它们可能导致死锁。见Eli Bendersky的two articles(来自@JBernardo)。

只是没有反应

如果用户已触发中止操作,则最简单的是无法响应,并且在打开操作完成之前不会对请求执行操作。

您的用户是否可接受此无响应(提示:否!),取决于您的项目。

它也会继续在服务器上发出需求,即使已知结果是不需要的。

让它在另一个线程中消失。

如果创建一个单独的线程来运行操作,然后以可中断的方式与该线程通信,则可以丢弃被阻塞的线程,然后开始处理下一个操作。最终,线程将解除阻塞,然后它可以正常关闭。

该线程应该是守护程序,因此它不会阻止应用程序的完全关闭。

这将为用户提供响应,但这意味着需要继续支持它的服务器,即使不需要结果。

将套接字方法重写为基于轮询的。

如@ Luke的answer所述,有可能为标准Python库提供(脆弱的,不可移植的?)扩展。

他的解决方案将套接字操作从阻塞更改为轮询。另一个可能允许通过socket.shutdown()方法关闭(如果确实如此,将中断被阻塞的套接字 - 未经测试。)

基于Twisted的解决方案可能更清晰。见下文。

用异步,非基于线程的库替换套接字。

Twisted框架为事件驱动的网络操作提供了一组替换库。我理解这意味着所有不同的通信都可以由一个没有阻塞的单线程处理。

破坏活动

可以导航OpenerDirector,找到阻塞的基本级别套接字,并直接破坏它(会socket.shutdown()是否足够?)以使其返回。

呸。

将其置于单独(可计费)的过程中

读取套接字的线程可以移动到单独的进程中,并且可以使用进程间通信来传输结果。这个IPC可以由客户提前中止,然后整个过程就可以被杀死。

要求Web服务器取消

如果您可以控制正在读取的Web服务器,则可能会发送一条单独的消息,要求它关闭套接字。这应该导致被阻止的客户端做出反应。

答案 1 :(得分:3)

我没有看到任何内置机制来实现这一目标。我只是将OpenerDirector移动到它自己的线程进程,因此杀死它是安全的。

注意:没有办法在python中“杀死”一个线程(感谢JBernardo)。然而,可能可以在线程中generate an exception,但如果线程在套接字上阻塞,则可能这不起作用。

答案 2 :(得分:2)

这是另一种方法的开始。它的工作原理是扩展httplib堆栈的一部分,以包含对服务器响应的非阻塞检查。您必须进行一些更改才能在您的线程中实现此功能。另请注意,它使用了一些未记录的urllib2和httplib,因此最终的解决方案可能取决于您使用的Python版本(我有2.7.3)。在urllib2.py和httplib.py文件中查看;它们非常易读。

import urllib2, httplib, select, time

class Response(httplib.HTTPResponse):
    def _read_status(self):
        ## Do non-blocking checks for server response until something arrives.
        while True:
            sel = select.select([self.fp.fileno()], [], [], 0)
            if len(sel[0]) > 0:
                break
            ## <--- Right here, check to see whether thread has requested to stop
            ##      Also check to see whether timeout has elapsed
            time.sleep(0.1)
        return httplib.HTTPResponse._read_status(self)

class Connection(httplib.HTTPConnection):
    response_class = Response

class Handler(urllib2.HTTPHandler):
    def http_open(self, req):
        return self.do_open(Connection, req)

h = Handler()
o = urllib2.build_opener(h)
f = o.open(url)
print f.read()

另请注意,堆栈中有许多位置可能会阻塞;此示例仅涵盖其中一个 - 服务器已收到请求但需要很长时间才能响应。

答案 3 :(得分:0)

我找到了一种方法,将所有与urllib相关的作业放在最合适的线程中,因为阻止了urllib的性质。然后可以完全中止任务,包括请求。杀死线程确实不安全,但异常提高应该是安全的。

这就是如何在线程(doc)中引发异常:

import ctypes
ctypes.pythonapi.PyThreadState_SetAsyncExc(ctypes.c_long(your_thread.ident),
                                           ctypes.py_object(your_exception))

如果此时套接字处于阻塞(连接)状态,则线程重新生效后会立即引发异常。