を使用して を作成していurllib2
ます。を使用して遅いページをフェッチしているため、タイムアウトが長くなります。build_opener()
OpenerDirector
OpenerDirector
ここまでは順調ですね。
ただし、別のスレッドで、ダウンロードを中止するように言われました。ユーザーが GUI でプログラムを終了することを選択したとしましょう。
urllib2 のダウンロードを終了する必要があることを知らせる方法はありますか?
明確な答えはありません。いくつかの醜いものがあります。
当初、私は却下されたアイデアを質問に入れていました。正しい答えがないことが明らかになったので、さまざまな次善の選択肢をリストの答えとして投稿することにしました。これらのいくつかはコメントに触発されています、ありがとう。
理想的な解決策はOpenerDirector
、キャンセル オペレーターが提供された場合です。
そうではありません。ライブラリの作成者は注意してください: 長くて遅い操作を提供する場合、人々が実際のアプリケーションでそれらを使用する場合、それらをキャンセルする方法を提供する必要があります。
他の人のための一般的な解決策として、これはうまくいくかもしれません。タイムアウトが小さいほど、状況の変化により敏感になります。ただし、タイムアウト時間内にダウンロードが完全に終了していない場合、ダウンロードが失敗する原因にもなるため、これはトレードオフです。私の状況では、それは受け入れられません。
繰り返しますが、一般的な解決策として、これは機能する可能性があります。ダウンロードが非常に大きなファイルで構成されている場合は、それらを小さなチャンクで読み取り、チャンクが読み取られた後に中止することができます。
残念ながら、(私の場合のように)遅延がファイルのサイズではなく、最初のバイトの受信にある場合、これは役に立ちません。
オペレーティング システムによっては、スレッドを強制終了する積極的な手法がいくつかありますが、推奨されません。特に、デッドロックが発生する可能性があります。Eli Bendersky の2 つの 記事(@JBernardo 経由) を参照してください。
アボート操作がユーザーによってトリガーされた場合は、単に応答せず、オープン操作が完了するまで要求に応じないのが最も簡単な場合があります。
この無反応がユーザーに受け入れられるかどうか (ヒント: いいえ!) は、プロジェクト次第です。
また、結果が不要であることがわかっている場合でも、サーバーに要求を出し続けます。
操作を実行する別のスレッドを作成し、そのスレッドと割り込み可能な方法で通信する場合、ブロックされたスレッドを破棄し、代わりに次の操作の作業を開始できます。最終的に、スレッドのブロックが解除され、正常にシャットダウンできるようになります。
スレッドはデーモンである必要があるため、アプリケーションの完全なシャットダウンをブロックしません。
これにより、ユーザーの応答性が向上しますが、結果が必要ない場合でも、サーバーはそれをサポートし続ける必要があることを意味します。
@Luke's answerで説明されているように、標準の Python ライブラリに (壊れやすい?、移植できない?) 拡張機能を提供できる場合があります。
彼のソリューションは、ソケット操作をブロッキングからポーリングに変更します。別の方法では、メソッドを介してシャットダウンを許可するsocket.shutdown()
場合があります (実際にブロックされたソケットが中断される場合 - テストされていません)。
Twisted に基づくソリューションは、よりクリーンな場合があります。下記参照。
Twistedフレームワークは、イベント駆動型のネットワーク操作用の代替ライブラリ セットを提供します。これは、さまざまな通信のすべてをブロックなしで単一のスレッドで処理できることを意味することを理解しています。
をナビゲートしてOpenerDirector
、ブロックしているベースレベル ソケットを見つけ、それを直接妨害して (socket.shutdown()
十分でしょうか?) 戻すことができる場合があります。
うん。
ソケットを読み取るスレッドを別のプロセスに移動し、プロセス間通信を使用して結果を送信できます。この IPC はクライアントによって早期に中止される可能性があり、その後、プロセス全体が強制終了される可能性があります。
読み取られている Web サーバーを制御できる場合は、ソケットを閉じるように要求する別のメッセージが送信される可能性があります。これにより、ブロックされたクライアントが反応するはずです。
これを達成するための組み込みメカニズムはありません。OpenerDirector を独自のスレッドプロセスに移動するだけで、安全に強制終了できます。
注: Python でスレッドを「kill」する方法はありません (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()
また、ブロックされる可能性のある場所がスタック内に多数あることにも注意してください。この例では、そのうちの 1 つだけを取り上げています。サーバーは要求を受け取りましたが、応答に時間がかかります。
urllib のブロック性のため、すべての urllib 関連のジョブをスレッドに配置するアプローチが最も適切であることがわかりました。その後、リクエストを含むタスクを完全に中止することができます。スレッドの強制終了は確かに安全ではありませんが、例外の発生は安全なはずです。
したがって、これはスレッド ( doc )で例外を発生させる方法です。
import ctypes
ctypes.pythonapi.PyThreadState_SetAsyncExc(ctypes.c_long(your_thread.ident),
ctypes.py_object(your_exception))
その時点でソケットがブロック (接続) 状態にある場合、スレッドが再び有効になった直後に例外が発生します。