雑記この広告は、90日以上更新していないブログに表示しています。2017-01-27Pythonの非同期通信(asyncioモジュール)入門を書きましたpythonTweetはじめに非同期処理のことから知らない人向けにPythonくらいしかろくに知らない人間が書きました。キーワードが文法に組み込まれたんだから理解したいじゃんか!asyncioモジュールを使うための基本的な概念が理解できるようになってるはず、多分。以上を想定しています。キーワードを使っていきます。それから、関数やメソッドの仮引数は全く書いてません。必要最低限は文中で説明していますが、より完全に知りたい人は適宜ドキュメントの参照をお願いします。主役はループちゃんasyncioの主役はイベントループです。イベントループは頼まれた仕事を順番にどんどん処理していくデキるクールガイです。本質的に、私たちはイベントループに仕事を関数オブジェクトの形で与えていくだけです。asyncioモジュールの大部分は、私たちが仕事を関数以外の形でイベントループに与えるためにあります(もちろん内部では関数としてイベントループに仕事を与えます)。後々やりますが、ほとんどの場合、私たちはコルーチンという形で仕事を定義し、それを加工してもらいます。asyncio.get_event_loop()で手に入れます。仕事の開始時間を決めることもできます。呼ぶようにイベントループにお願いします。それ以前の仕事に時間がかかっている場合は、この仕事に手を付けるのは開始時間以降になるので注意です。イベントループに仕事を頼みこんだあとは、イベントループを実行して仕事を処理していってもらいましょう!loop.run_forever()によって、ループを永遠に実行します。永遠に実行されると困るので、なんらかの形でイベントループを止めるようにしましょう。loop.stop()が呼ばれると、イベントループはその時点に積んである仕事をすべて終わらせてから停止します。その仕事をこなす途中で新たに追加された仕事は、次回の実行のときまで延期されます。残業中に出てきた新しい仕事は、次の出社日にやるってことですね。最後の仕事としてループの停止をさせる最後の仕事として渡した関数の中身でloop.stop()を呼ぶなどをしてループに退社命令を出してあげましょう。停止したループはloop.close()で閉じておきましょう。させるか、停止させるか)ループを走らせる時点では、ループには1つの仕事しか与えていませんが、その仕事の中で次の仕事をループに与えているという構図です。ループを邪魔しない限り、永遠にループは時刻を表示しつづけます。Handleクラスのインスタンスを返します。その仕事をキャンセルしたい場合は、そのハンドルのcancel()メソッドが使えます。asyncio.get_event_loop())イベントループへのスケジューリング(loop.call_soon(callback)とか)イベントループの実行(loop.run_forever()など)イベントループの停止(loop.stop())イベントループを閉じる(loop.close())ということです。ループちゃんに仕事をさせるのかここまでにしたことだけを見れば、私たちがふつうに関数を順次実行していくようにコードを書くのと何ら変わりません。器用って?というのも、ループはブロッキングI/Oについて上手くこなす術を知っているんです。I/Oはともかくとして、ブロッキングというのは日常生活でもよく遭遇し、私たちをイライラさせていることでしょう。あなたが何かを待っている間、待つこと以外にろくなことができないとき、あなたはブロッキングされています!市役所の長い長い処理時間の間、あなたにできることと言えば鋭い眼差しで事務員にプレッシャーを与えることくらいでしょう。処理が終わったらケータイに連絡をくれると言ってくれたらどうでしょう?その間にあなたは今日発売のお気に入りの雑誌を買いに書店へ行き、今月分の家賃を振込み、家になかった文房具を調達し、懸賞ハガキをポストに入れ、喫茶店でコーヒーを飲みながらasyncioに関する記事を書き、......。実に待ち時間を有意義に過ごすことができたことでしょう!いや、ケータイに連絡をくれなくっても、せめて受付番号を渡してくれれば、仕事を1つこなしては市役所に行って自分の処理が終わってないか確認し、まだなら他のことをしにいくというようなことができますよね。いちいち市役所に行かないといけないのが面倒ですが、やるべきことが市役所のごく近所で済むようなら、まあ十分でしょう。イベントループは、まさにI/O(と一部の事柄)に関してこのような器用なことができるのです。時間のかかるI/Oの前でいつまでも突っ立ってないで、イベントループはそのチェックを他の仕事の後に回し、次の仕事に取りかかります。調べたところによると、あえて区別するのであれば、ケータイに連絡してくれるような方式を「非同期I/O」、受付番号をもらって度々確認しにいくような方式を「ノンブロッキングI/O」と呼ぶようです。ちなみに最高にストレスのたまる例のアレは「同期I/O」や「ブロッキングI/O」と呼びます。asyncioモジュールのイベントループは基本的にはノンブロッキングに仕事をこなします。ある場合は非同期的にも振る舞いますが、いずれにせよイベントループは仕事を無駄なく処理しようとしてくれます。ループちゃんのスキルを生かす今までのやり方だとループちゃんの器用さがまるで生かされてないのは明らかです。今までの、関数を仕事として渡すという素朴なやりかたではループちゃんは「空き時間」を上手く見つけられないのです。そこで、言わずもがなですが、次にループちゃんが器用に動き回れるような仕事の作り方を学びましょう。...と、その準備として、ちょっと不思議なオブジェクト、Futureオブジェクトについて学びましょう。あるオブジェクトです。ある値を渡すような処理があったとして、送信側はとりあえずFutureオブジェクトを受信側に渡し、送信側は処理が終わり次第そのFutureオブジェクトに値を放り込みます。受信側は、Futureオブジェクトの状態が完了になり次第、値を取り出して続きの処理に使うことができます。イベントループの器用さは確かにこのFutureオブジェクトに因るところが大きいのですが、これについてはもう少し後で見ていきましょう。結果か例外が設定されたことを意味します。コールバックはFutureオブジェクトが完了したときに呼び出されます。なり、コールバックも呼ばれません。Futureオブジェクトをより輝かしいものとしてくれているのは、完了時コールバックの機能です!コールバックは、Futureオブジェクトが完了したときに呼ばれる処理です。このコールバックにはFutureオブジェクト自身のみを引数として取るような関数(あるいは一般にそのようなコーラブルオブジェクト)を指定します。デフォルトなFutureオブジェクトはloop.create_future()で作ります。Futureオブジェクトを作った後、あるいは誰かから渡された後、コールバックを設定してお好みのFutureオブジェクトを作り上げましょう!Futureオブジェクトを利用した上手い方法は2つあります。ひとつは、定期的にFutureオブジェクトの様子を見てもらうようにイベントループに仕事を頼む方法です。完了していないようであれば、同じ仕事をcall_soonして、しばらくしてからまたイベントループに見てもらうようにします。ひとつは、恐らく分かっているとは思いますが、完了時コールバックを使う方法です。Futureオブジェクトに、Futureオブジェクトが完了して以降にしたかった仕事内容をコールバック関数として渡しておきます。するとFutureオブジェクトは自分に値が設定されたときに、イベントループにその仕事を頼みます。ケータイに連絡するように!イベントループはその仕事をスケジュールに組み込んで、めでたしめでたし。完了時コールバックを用いた方が断然スタイリッシュにみえますが、前者の方でしかできないこともあります。2つの方法を使い分けられるようにしましょう。例Futureオブジェクトを利用した便利な機能のひとつが、イベントループの実行の仕方のひとつ、loop.run_until_complete(future)です。loop.run_forever()と異なり、引数のFutureオブジェクトが完了するとイベントループが停止します。似たような挙動のものが自前でも作れそうです。準備loop.run_forever()実は、loop.run_until_complete()にはまだ隠された機能があるのですが、これは後々明らかになるでしょう。上のコードはこのメソッドの完全版ではありません。result)print(result)loop.close()あ、実はloop.run_until_complete(future)は完了したFutureオブジェクトの結果の値、すなわちfuture.result()を返してくれます。今回の場合だと、Futureオブジェクトは手元にあるので、そこから結果を取り出しても構いません。引数としてともに与えるとなります。ループの唯一のお仕事はrandom_hitです。これは乱数値を取得し、それが1ならFutureオブジェクトにcountの値をセット、そうでないならcountを+1し、再度同じ条件でrandom_hitをループに仕事して与えます。random_hitはloopが与えられなかったときに自分でasyncio.get_event_loop()によってイベントループを取得しているところに注意してください。イベントループは1スレッドに1つ与えられるので、ここで得られるイベントループは最初に取得したものと同じです。