2017-08-03 9 views
5

私はウェブページをクロールするためにスパイダーを書いています。私はasyncioが多分私の最高の選択を知っています。だから、コルーチンを使って作業を非同期に処理します。今私はキーボード割り込みによってプログラムを終了する方法について私の頭を傷つける。すべての作業が終わった後、プログラムは正常に終了する可能性があります。ソースコードはPython 3.5で動かすことができ、以下にアタッチされています。Ctrl + Cでコルーチンを正常にシャットダウンするには?

import asyncio 
import aiohttp 
from contextlib import suppress 

class Spider(object): 
    def __init__(self): 
     self.max_tasks = 2 
     self.task_queue = asyncio.Queue(self.max_tasks) 
     self.loop = asyncio.get_event_loop() 
     self.counter = 1 

    def close(self): 
     for w in self.workers: 
      w.cancel() 

    async def fetch(self, url): 
     try: 
      async with aiohttp.ClientSession(loop = self.loop) as self.session: 
       with aiohttp.Timeout(30, loop = self.session.loop): 
        async with self.session.get(url) as resp: 
         print('get response from url: %s' % url) 
     except: 
      pass 
     finally: 
      pass 

    async def work(self): 
     while True: 
      url = await self.task_queue.get() 
      await self.fetch(url) 
      self.task_queue.task_done() 

    def assign_work(self): 
     print('[*]assigning work...') 
     url = 'https://www.python.org/' 
     if self.counter > 10: 
      return 'done' 
     for _ in range(self.max_tasks): 
      self.counter += 1 
      self.task_queue.put_nowait(url) 

    async def crawl(self): 
     self.workers = [self.loop.create_task(self.work()) for _ in range(self.max_tasks)] 
     while True: 
      if self.assign_work() == 'done': 
       break 
      await self.task_queue.join() 
     self.close() 

def main(): 
    loop = asyncio.get_event_loop() 
    spider = Spider() 
    try: 
     loop.run_until_complete(spider.crawl()) 
    except KeyboardInterrupt: 
     print ('Interrupt from keyboard') 
     spider.close() 
     pending = asyncio.Task.all_tasks() 
     for w in pending: 
      w.cancel() 
      with suppress(asyncio.CancelledError): 
       loop.run_until_complete(w) 
    finally: 
     loop.stop() 
     loop.run_forever() 
     loop.close() 

if __name__ == '__main__': 
    main() 

実行中に 'Ctrl + C'を押すと、奇妙なエラーが発生することがあります。私はときどきプログラムが正常に 'Ctrl + C'でシャットダウンできることを意味します。エラーメッセージは表示されません。しかし、場合によっては、 'Ctrl + C'を押してもプログラムが実行されていて、すべての作業が完了するまで停止しないことがあります。その時点で「Ctrl + C」を押すと、「タスクは破棄されましたが保留中です!」そこにいるだろう。

私はasyncioに関するいくつかのトピックを読んでおり、コルーチンを正常に閉じるためにmain()にいくつかのコードを追加しました。しかし、それは動作しません。他の誰かにも同様の問題がありますか?

答えて

3

私はこの問題は、ここで起こる賭ける:

except: 
    pass 

あなたはshould never doようなもの。そしてあなたの状況は、別のことが起こる可能性のもう一つの例です。

タスクをキャンセルしてキャンセルを待っているときは、asyncio.CancelledErrorはタスク内に、shouldn't beは内部のどこにも表示されません。タスクのキャンセルを待っている行は、この例外を発生させるはずです。さもなければ、タスクは実行を継続します。

あなたが実際にタスクをキャンセルする

task.cancel() 
with suppress(asyncio.CancelledError): 
    loop.run_until_complete(task) # this line should raise CancelledError, 
            # otherwise task will continue 

を行う理由です。

UPD:

しかし、元のコードは、不確実な確率で 'Ctrlキー+ C' によってもやめなかった理由私はまだほとんど理解していませんか?

それあなたのタスクの状態の依存性:

  1. 瞬間にあなたは それらの非が待っているにCancelledErrorを発生させますし、あなたのコードが終了し、すべてのタスクが実行されている「Ctrlキー+ C」を押すと、通常は。
  2. 「Ctrl + C」を押すと、いくつかのタスクが保留中ですが、実行が終了すると、コードはタスクのキャンセルにちょっとぶつかり、直後にタスクが終了すると終了します。
  3. 「Ctrl + C」を押しているタスクが保留中で、完了するまでに が残っていると、コードはこれらのタスクをキャンセルしようとしています( は実行できません)。もう1つの 'Ctrl + C'は のプロセスを中止しますが、タスクはキャンセルされずに終了し、 'タスクは破棄されましたが保留中です!'という警告が表示されます。
+0

あなたが正しいと思います。 'except:pass'の場合​​です!私は 'except'に 'pass'の後に 'raise'を追加し、 'Ctrl + C'でうまく終了することができます。したがって、エラーをログに記録する場合は、main()がasyncio.CancelledErrorなどのこれらの例外をキャッチできるように、例外を再評価する必要があります。しかし、私はまだ、元のコードが不確実な確率で 'Ctrl + C'でうまく終了できない理由をほとんど理解していませんか? fetch()の 'try-except'構造体がすべての例外を捕捉できる場合、main()は何も取得しません。その結果、エラーは毎回発生します。 – xssl

+0

@xssl、別のケースで何が起こる可能性があるかを示す答えを更新しました。 –

0

あなたはUnixのフレーバーを使用していると仮定します。そうでない場合、私のコメントはあなたの状況に当てはまらないかもしれません。Ctrlキー押す

- 端末におけるCこのTTY信号SIGINTに関連付けられているすべてのプロセスを送ります。 PythonプロセスがこのUnixシグナルを捕まえ、これをKeyboardInterrupt例外をスローするように変換します。スレッド化されたアプリケーション(私はasyncのものが内部的にスレッドを使用しているのかどうかはわかりませんが、そういうもののように聞こえます)では通常、この信号を受け取ってこのように反応します。特にこの状況のた​​めに用意されていない場合、例外のために終了します。

スレッド管理者は、実行中のスレッドが終了するのを待ってから、Unixプロセス全体が終了コードで終了します。これにはかなりの時間がかかります。 this question about killing fellow threadsを参照してください。これは一般的に不可能な理由は何ですか。

あなたがしたいことは、あなたのプロセスをただちに終了させ、すべてのスレッドを1つのステップで終了させることです。 \ -

これを達成する最も簡単な方法は、Ctrlキーを押すことです。これにより、SIGINTの代わりにSIGQUITが送信されます。これは、通常、仲間のスレッドにも影響を与え、それらを終了させます。

これが(何らかの理由であなたがCtrlキーで適切に反応する必要があるため、 - C)が十分でない場合は、あなたが自分自身にシグナルを送ることができます。

import os, signal 

os.kill(os.getpid(), signal.SIGQUIT) 

これがない限り、すべての実行中のスレッドを終了する必要があります彼らは特にSIGQUITをキャッチしますが、その場合でもまだSIGKILLを使用して強制的に強制終了できます。これは、彼らに反応の任意のオプションを与えるものではありませんし、問題につながる可能性があります。

関連する問題