Подтвердить что ты не робот

Как изящно прервать загрузку urllib2?

Я использую urllib2 build_opener() для создания OpenerDirector. Я использую OpenerDirector для получения медленной страницы и поэтому имеет большой тайм-аут.

До сих пор так хорошо.

Однако в другом потоке мне сказали прекратить загрузку - скажем, пользователь выбрал для выхода из программы в графическом интерфейсе.

Есть ли способ сообщить, что загрузка urllib2 должна прекратиться?

4b9b3361

Ответ 1

Нет чистого ответа. Есть несколько уродливых.

Вначале я выдвигал отвергнутые идеи в вопросе. Поскольку стало ясно, что нет правильных ответов, я решил опубликовать различные субоптимальные альтернативы в качестве ответа на список. Некоторые из них вдохновлены комментариями, спасибо.

Поддержка библиотеки

Идеальное решение было бы, если OpenerDirector предложил оператор отмены.

Это не так. Авторы библиотек отмечают: если вы обеспечиваете длительные медленные операции, вам нужно предоставить способ их отмены, если люди будут использовать их в реальных приложениях.

Уменьшить время ожидания

Как общее решение для других, это может сработать. При меньшем тайм-ауте он будет более восприимчивым к изменениям обстоятельств. Однако это также приведет к сбою загрузки, если они не были полностью завершены в тайм-аут, поэтому это компромисс. В моей ситуации это несостоятельно.

Прочитайте загрузку в кусках.

Опять же, как общее решение, это может сработать. Если загрузка состоит из очень больших файлов, вы можете читать их в небольших кусках и прервать после чтения фрагмента.

К сожалению, если (как и в моем случае) задержка заключается в получении первого байта, а не в размере файла, это не поможет.

Убить весь поток.

В то время как есть некоторые агрессивные методы для уничтожения потоков, в зависимости от операционной системы они не рекомендуются. В частности, они могут привести к возникновению взаимоблокировок. См. Eli Bendersky два статьи (через @JBernardo).

Просто не отвечайте

Если операция прерывания была инициирована пользователем, может быть проще просто не отвечать на запросы и не действовать по запросу до завершения открытой операции.

Является ли эта неприкосновенность приемлемой для ваших пользователей (подсказка: нет!), зависит от вашего проекта.

Он также продолжает размещать запрос на сервере, даже если результат известен как ненужный.

Позволяет переходить в другой поток.

Если вы создаете отдельный поток для запуска операции, а затем обмениваетесь этим потоком прерывистым способом, вы можете отказаться от заблокированного потока и начать работу над следующей операцией. В конце концов, поток будет разблокирован, а затем он может изящно отключиться.

Поток должен быть демоном, поэтому он не блокирует полное закрытие приложения.

Это даст пользователю отзывчивость, но это означает, что сервер должен будет продолжать поддерживать его, даже если результат не нужен.

Перепишите методы сокетов на основе опроса.

Как описано в @Luke answer, может быть возможно предоставить (хрупкие?, unportable?) расширения в стандартные библиотеки Python.

Его решение изменяет операции сокета от блокировки до опроса. Другой может позволить завершить работу с помощью метода socket.shutdown() (если это действительно приведет к прерыванию заблокированного сокета - не проверено.)

Решение на основе Twisted может быть более чистым. См. Ниже.

Замените сокеты асинхронными, не потоковыми библиотеками.

Twisted framework предоставляет заменяемый набор библиотек для сетевых операций, управляемых событиями. Я понимаю, это означает, что все разные коммуникации могут обрабатываться одним потоком без блокировки.

Саботаж

Возможно, можно перемещаться по OpenerDirector, чтобы найти блокировку базового уровня, которая блокирует, и саботировать ее напрямую (будет ли socket.shutdown() достаточным?), чтобы вернуть ее.

Тьфу.

Поместите его в отдельный (убивающий) процесс

Поток, который считывает сокет, может быть перенесен в отдельный процесс, и для передачи результата может использоваться межпроцессная связь. Этот IPC может быть прерван раньше клиентом, а затем весь процесс может быть убит.

Попросите веб-сервер отменить

Если вы контролируете считываемый веб-сервер, ему может быть отправлено отдельное сообщение с просьбой закрыть сокет. Это должно заставить заблокированный клиент реагировать.

Ответ 2

Я не вижу встроенного механизма для этого. Я просто переместил бы OpenerDirector в свой собственный процесс thread, поэтому было бы безопасно его убить.

Примечание: нет способа "убить" поток в python (спасибо JBernardo). Тем не менее, возможно, генерировать исключение в потоке, но, скорее всего, это не сработает, если поток блокирует сокет.

Ответ 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))

Если сокет в данный момент будет находиться в состоянии блокировки (соединения), исключение будет возбуждено сразу же после возобновления потока.