【问题标题】:How do I gracefully interrupt urllib2 downloads?如何优雅地中断 urllib2 下载?
【发布时间】:2012-08-05 14:45:41
【问题描述】:

我正在使用urllib2build_opener() 创建一个OpenerDirector。我正在使用OpenerDirector 来获取一个缓慢的页面,因此它有很大的超时。

到目前为止,一切都很好。

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

有没有办法指示 urllib2 下载应该退出?

【问题讨论】:

  • 更多关于原因的信息会很有用——正如你所说,杀死一个线程真的是不可取的,其他方法取决于你是否可以守护进程,然后有一个共享队列等。 .
  • Eli Bendersky 有很多关于停止 Python 线程工作的文章。阅读herehere
  • @JBernardo:嗯,那已经过去了,这个计划破灭了。 TerminateThread(windows)会导致死锁吗?啊。我说它很丑,但我没有意识到它会很危险。
  • @Jon:我不确定您可能需要什么进一步的信息。一个线程正在获取返回缓慢的网页。另一个线程的任务是关闭它,因为用户取消了。守护进程 threads 只会在整个应用程序关闭时工作,但这可能很有用。
  • @Oddthinking 这不是 Python 问题。您尝试的每种语言都会警告杀死线程(因为内存泄漏)或不允许您这样做。如果性能不是问题,您可以使用multiprocess

标签: python real-time urllib2


【解决方案1】:

没有明确的答案。有几个丑的。

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

图书馆支持

如果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() 就足够了吗?)使其返回。

呸。

把它放在一个单独的(可杀死的)进程中

读取socket的线程可以移动到一个单独的进程中,可以使用进程间通信来传输结果。这个IPC可以被客户端提前中止,然后整个进程可以被杀死。

请求 Web 服务器取消

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

【讨论】:

    【解决方案2】:

    我没有看到任何内置机制来实现这一点。我只是将 OpenerDirector 移到它自己的 thread 进程中,这样可以安全地杀死它。

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

    【解决方案3】:

    这是另一种方法的开始。它通过扩展 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()
    

    还要注意堆栈中有很多地方可能会阻塞;这个例子只涉及其中之一——服务器已经收到请求但需要很长时间才能响应。

    【讨论】:

      【解决方案4】:

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

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

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

      如果套接字此时处于阻塞(连接)状态,则在线程再次活跃后将立即引发异常。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-12-20
        • 1970-01-01
        • 1970-01-01
        • 2010-11-29
        • 1970-01-01
        相关资源
        最近更新 更多