我正在使用urllib2
'sbuild_opener()
创建一个OpenerDirector
. 我正在使用OpenerDirector
来获取慢速页面,因此它的超时时间很长。
到现在为止还挺好。
但是,在另一个线程中,我被告知要中止下载 - 假设用户已选择退出 GUI 中的程序。
有没有办法指示 urllib2 下载应该退出?
没有明确的答案。有几个丑的。
最初,我将被拒绝的想法放在问题中。由于很明显没有正确的答案,我决定将各种次优选择作为列表答案发布。其中一些受到评论的启发,谢谢。
一个理想的解决方案是OpenerDirector
提供一个取消操作符。
它不是。库作者请注意:如果您提供长时间缓慢的操作,那么如果人们要在实际应用程序中使用它们,您需要提供一种取消它们的方法。
作为其他人的通用解决方案,这可能会起作用。超时时间越小,它就越能响应环境的变化。但是,如果在超时时间内没有完全完成,也会导致下载失败,所以这是一个权衡。在我的情况下,这是站不住脚的。
同样,作为一般解决方案,这可能有效。如果下载包含非常大的文件,您可以分小块读取它们,并在读取块后中止。
不幸的是,如果(在我的情况下)延迟是接收第一个字节,而不是文件的大小,这将无济于事。
虽然有一些激进的技术可以杀死线程,但取决于操作系统,不推荐使用它们。特别是,它们可能导致发生死锁。请参阅 Eli Bendersky 的两篇 文章(来自 @JBernardo)。
如果用户触发了中止操作,最简单的方法可能是不响应,直到打开操作完成后才对请求进行操作。
您的用户是否可以接受这种无响应(提示:不!),取决于您的项目。
即使已知结果是不需要的,它也会继续对服务器提出要求。
如果您创建一个单独的线程来运行该操作,然后以可中断的方式与该线程通信,您可以丢弃阻塞的线程,并开始执行下一个操作。最终,线程将解除阻塞,然后它可以优雅地关闭。
该线程应该是一个daemon,因此它不会阻止应用程序的完全关闭。
这将给予用户响应,但这意味着需要继续支持它的服务器,即使不需要结果。
如@Luke's answer中所述,可以为标准 Python 库提供(脆弱的?不可移植的?)扩展。
他的解决方案将套接字操作从阻塞更改为轮询。另一个可能允许通过该方法关闭socket.shutdown()
(如果确实会中断阻塞的套接字 - 未经测试。)
基于 Twisted 的解决方案可能更清洁。见下文。
Twisted框架为事件驱动的网络操作提供了一组替代库。我理解这意味着所有不同的通信都可以由一个没有阻塞的单线程处理。
可以导航OpenerDirector
, 以找到阻塞的基本套接字,并直接破坏它(socket.shutdown()
就足够了吗?)使其返回。
呸。
读取套接字的线程可以移动到一个单独的进程中,并且可以使用进程间通信来传输结果。这个IPC可以被客户端提前中止,然后整个进程可以被杀死。
如果您可以控制正在读取的 Web 服务器,则可以向它发送一条单独的消息,要求它关闭套接字。这应该会导致被阻止的客户端做出反应。
我没有看到任何内置机制来实现这一点。我只是将 OpenerDirector 移到它自己的线程进程中,这样就可以安全地杀死它。
注意:没有办法在 python 中“杀死”一个线程(感谢 JBernardo)。但是,可能会在线程中生成异常,但如果线程在套接字上阻塞,这可能不起作用。
这是另一种方法的开始。它通过扩展 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()
另请注意,堆栈中有许多可能会阻塞的地方;这个例子只涉及其中之一——服务器已经收到请求但需要很长时间才能响应。
由于 urllib 的阻塞性质,我找到了一种将所有与 urllib 相关的作业放在最合适的线程中的方法。然后可以完全中止任务,包括请求。杀死线程确实不安全,但引发异常应该是安全的。
所以这是如何在线程( doc )中引发异常:
import ctypes
ctypes.pythonapi.PyThreadState_SetAsyncExc(ctypes.c_long(your_thread.ident),
ctypes.py_object(your_exception))
如果此时套接字处于阻塞(连接)状态,则在线程再次活跃后将立即引发异常。