イベントループ

イベントループ 、メッセージディスパッチャ 、メッセージループ 、メッセージポンプ 、ランループ  とは、プログラム内でイベントやメッセージを待ち受け、それらをディスパッチする構成要素である。内部または外部の「イベントプロバイダー」に要求することで動作し、次いで適当なイベントハンドラー  を呼び出す。イベントプロバイダーが後述のファイルインタフェースに従う場合、イベントループは reactor と連携する形で使われることがあり、select または poll を使ってファイルインタフェースにアクセスする。イベントループはほぼ常にメッセージの発信元とは非同期に動作する。

イベントループはプログラムの中心的制御構造となっていることが多い。そのためそれをメインループ  またはメインイベントループ  とも呼ぶ。そのようなプログラムではイベントループが最上位の制御構造となっており、そのため「メイン」と名づけられている。

1. メッセージパッシング
-------------

メッセージポンプという呼称は、プログラムのメッセージキューからメッセージを汲み上げ、そのプログラム内で処理することに由来する。厳密には、イベントループはプロセス間通信の実装法の1つである。実のところメッセージ処理は多くのシステムに存在し、例えばMachのカーネルレベルのコンポーネントにもある。イベントループはメッセージを使用するシステムの実装技法の1つである。

2. 代替手法
-------

この手法は以下のような他の手法とは対照的である:

古くからある、単純に一回動作して終了するプログラム。この種のプログラムは情報処理の最初期からあり、ユーザーとの対話手段を持たない。現在も主にCUI指向のプログラムでよく使われている。各種パラメータを指定して起動される。
メニュー駆動型設計。この場合も一種のメインループは存在するが、ユーザーから見てイベント駆動的ではない。イベント駆動の代わりとして、階層型のメニューを順次選択していって、希望する動作を指定する。このメニューを通した限定的な対話性がある。

3. 例
----

GUIが主流となったため、多くのアプリケーションがメインループを持つようになった。get_next_message というルーチンは一般にOSが提供するもので、メッセージが到着するまでブロックされる。したがってこのループが動作するのは処理すべきものが存在する場合だけである。

function main initializewhile message != quit message := get_next_message process_messageend whileend function

4. ファイルインタフェース
--------------

UNIXでは「あらゆるものはファイルである」というパラダイムにより、ファイルベースのイベントループが自然に生まれた。ファイルの読み書きだけでなく、プロセス間通信、ネットワーク通信、デバイス制御が全てファイルI/Oで行われ、対象はファイル記述子で指定される。selectおよびpollシステムコールを使えば、複数のファイル記述子の状態変化を同時に監視でき、読み込むべきデータが到着したことを検知できる。

例として、継続的に更新されるファイルから読み込んでその内容を X Window System に表示するPythonプログラムを示す。クライアントとはソケットを通じて通信する。 main: file_fd = open  x_fd = open_display  construct_interface  while changed_fds = select : if file_fd in changed_fds: data = read_from  append_to_display  send_repaint_message  if x_fd in changed_fds: process_x_messages 

4.1. シグナル処理
-----------

UNIXでファイルインタフェースに従わない数少ない例として、非同期イベントがある。シグナルはシグナルハンドラで受信する。シグナルハンドラは小さな制限されたコードであり、それが動作中はプログラム本体の処理はサスペンドされる。selectでブロック中にシグナルを受信して処理した場合、selectはEINTRというエラーコードを伴って早期に戻る。プログラムがCPUを使用している間にシグナルを受信すると、シグナルハンドラを実行する間は本体の実行がサスペンドされる。

したがってシグナルを考慮するには、シグナルハンドラで大域変数のフラグをセットし、イベントループのselect呼び出しの直前と直後でそのフラグをチェックすればよい。フラグがセットされていたら、ファイル記述子でのイベントと同様にシグナルを処理する。しかしながら、この技法では競合状態が生じる。フラグのチェックとselect呼び出しの間にシグナルが到着した場合、selectが他の理由で戻るまでシグナルを処理できない。

この問題を解決するため、POSIXではpselectシステムコールを提供している。これはselectに似ているがsigmaskという引数が追加されており、シグナルのマスクを設定できる。これを使えば、普段はシグナルをマスクしておき、selectを呼び出している間だけマスクを解除することができる。すると、シグナルは select がイベントを待ち受けている間だけ受信されることになる。ただし、pselectが利用可能となったのは比較的最近のことで、たとえばLinuxの場合、Linuxカーネルのバージョン2.6.16より以前の版ではpselectシステムコールは実装されておらず、glibcでは競合状態の問題をはらんだ実装がなされていた。

より汎用的な代替技法として、非同期イベントを//self-pipe trick//と呼ばれる技法でファイルベースのイベントに変換してやる技法がある。これはシグナルハンドラでパイプに1バイトを書き込み、そのパイプのもう一方の端を主プログラムがselectで監視するという技法である。Linuxカーネル 2.6.22では、signalfdという新システムコールが追加された。これはシグナル受信用の特別なファイル記述子を生成する。

5. 実装例
------

5.1. Windowsアプリケーション
--------------------

Windowsにてユーザーとやりとりするプロセスを動作させる場合、イベントに応答するためのメッセージループが必須である。Windowsではイベントとメッセージは同等視される。イベントとしては、ユーザーとのやりとり、ネットワークのトラフィック、システム処理、タイマー、プロセス間通信などがある。対話型でないI/Oのみのイベントについては、I/O完了ポートがある。I/O完了ポートのループはメッセージループとは別に動作し、メッセージループと相互作用することがない。

大抵のWin32アプリケーションの「心臓部」はWinMain関数であり、ループ内でGetMessageを呼び出す。GetMessageはメッセージまたは「イベント」を受信するまでブロックする。何らかの選択的処理の後DispatchMessageを呼び出し、対応するハンドラー用のコールバック関数  にメッセージをディスパッチする。専用のウィンドウプロシージャのないメッセージはDefWindowProcというデフォルトのハンドラーにディスパッチする。RegisterClassでウィンドウクラスを登録する際にウィンドウプロシージャの関数ポインタを指定することができ、DispatchMessageはメッセージの送信先ウィンドウハンドル  に対応するウィンドウプロシージャを呼び出す。

以下はMicrosoft Docs  に記載されている、メッセージループの実装例のひとつである: MSG msg; BOOL bRet;

while ) != 0) { if  { // handle the error and possibly exit } else { TranslateMessage; DispatchMessage; } }<WtXmlEndTag /> そのほか、ブロッキングせずにメッセージキューからメッセージを読み取るPeekMessageを使う方法もある。

5.1.1. メッセージの順序性
----------------

date=2019-08のWindowsでは、システムや周辺機器が受信した順序でメッセージループにメッセージが到達することを保証している。これはマルチスレッドアプリケーションの設計で必須となる。

ただし、一部のメッセージには異なる規則が適用され、常に最後に受信されるメッセージや文書化された特別な優先順位で受信されるメッセージがある。

5.1.2. フレームワーク
--------------

MFC、Windows Forms、WPFといったアプリケーションフレームワークでは、ライブラリ側で既定のメッセージループの実装を持っているため、通常はアプリケーションコードで明示的に記述する必要はない。アプリケーションで必要となるイベントハンドラーのみを記述していく「イベント駆動型プログラミング」のスタイルを用いて効率的に開発できる。ただし、必要に応じてメッセージループを詳細にカスタマイズしたり、異なるフレームワークを相互運用したりするためのAPIも用意されている。

5.2. Xlibのイベントループ
-----------------

Xlibを直接使用するXアプリケーションは、XNextEventファミリの関数を中心に構築される。XNextEventはイベントキューにイベントが到着するまでブロックし、イベントを受け取ると即座にアプリケーションが適切にそれを処理する。Xlibのイベントループが扱うのはウィンドウシステムのイベントのみである。他のファイルやデバイスについても待ち受ける必要がある場合は ConnectionNumber などのプリミティブからイベントループを自前で構築する必要があるが、一般にマルチスレッドを採用することが多い。

Xlibを直接使用するプログラムは少ない。より一般的にはXlib上に構築されたGUIツールキットを使用する。例えば、X Toolkit Intrinsics  上のツールキットでは XtAppAddInput と XtAppAddTimeout を使用する。

なお、シグナル受信時の状態が不定であるため、シグナルハンドラからXlib関数を呼び出すのは危険である。

5.3. GLibのイベントループ
-----------------

GLibのイベントループはGTK+向けに作られたが、今ではD-BusなどのGUI以外のアプリケーションでも使われている。ファイル記述子群で監視したいリソースを指定する。シグナルを受信するかタイムアウトすると、ブロック状態から復帰する。GLibはファイル記述子と子プロセス終了のイベントを組み込みでサポートしているが、prepare-check-dispatch モデルの対象として任意のイベントを加えることが可能である。

GLibのイベントループを使っているアプリケーションライブラリとしては、GStreamerやGnomeVFSの非同期IOメソッド群があるが、最大のクライアントライブラリはGTKである。ウィンドウシステムからのイベントはGTK+イベントに変換され、アプリケーションのウィジェットオブジェクト上のGLibシグナルとして発せられる。

5.4. macOSのループ
--------------

macOSではスレッド毎に1つのCFRunLoopがあり、それに任意個のソースとオブザーバを対応させることができる。そのループがメッセージのキューイングとディスパッチを行い、それを通してソースとオブザーバがやりとりする。

CocoaではCFRunLoopがNSRunLoopに抽象化されており、任意のメッセージをキューイングして任意のオブジェクトにディスパッチできる。

5.5. Androidのメッセージループ
---------------------

Java言語向けのAndroid SDKにおけるアプリケーションフレームワークには、メッセージを表現するandroid.os.Messageクラス、メッセージキューを表現するandroid.os.MessageQueueクラス、メッセージループの実装であるandroid.os.Looperクラス、メッセージの送信と処理を担当するandroid.os.Handlerクラスなどが用意されている。ただし、Looper.loopメソッドの実装に使われているMessageQueue.nextメソッドなどがAPIとして公開されていないため、メッセージループを独自に実装することはできない。

Android NDKでは、POSIXパイプとALooper関連APIを利用して、ネイティブスレッド上にメッセージループを独自に実装することができる。NativeActivityのサンプルには、ALooperを利用したメッセージループの実装が含まれている。

6. 脚注
-----

6.1. 注釈
-------

6.2. 出典
-------

7. 関連項目
-------

メッセージ 
イベント 
イベント駆動型プログラミング
プロセス間通信
非同期IO
モーダルウィンドウ

8. 外部リンク
--------

[1]
[2]