ソフトウェアにおいて、イベントループとは、イベントの制御フローを継続的にディスパッチするアルゴリズムです。ループはイベントプロバイダ(通常はイベントが発生するまでループをブロックします)から次のイベントを要求し、イベントを受信すると、それに関連付けられたイベントハンドラを呼び出します。イベントループがプログラムの中心的なイベントディスパッチャである場合(多くの場合そうであるように)、それはメインループまたはメインイベントループと呼ばれます。
ウェブブラウザやサーバーランタイムなどの現代の環境では、イベントループは、メインプログラムスレッドがアイドル状態のときにキューからイベントやメッセージを継続的に監視およびディスパッチすることで非同期実行を可能にする基本的なメカニズムです。JavaScriptでは、イベントループにより、シングルスレッド言語であるにもかかわらず、ユーザー操作、タイマー、I/O操作などのタスクをノンブロッキングで処理できます。[ 1 ]
同じアルゴリズムは、イベントの上位集合である受信メッセージの処理にも使用できます。この場合、アルゴリズムはメッセージループ、メッセージディスパッチャ、またはメッセージポンプと呼ばれます。メッセージループの一般的な用途は、メッセージキューがプログラム外部(オペレーティングシステムなど)で管理されるプロセス間メッセージパッシング 通信です。
一般的に、グラフィカルユーザーインターフェース(GUI)環境で動作するプログラムはイベントループを使用し、GUI環境が主流となっているため、現代のほとんどのアプリケーションはメインイベントループを備えています。
以下の擬似コードにおいて、get_next_message()は通常オペレーティングシステムによって提供される関数のプレースホルダーであり、メッセージが利用可能になるまで処理をブロックします。したがって、ループは処理すべきメッセージがある場合にのみ繰り返されます。
ループ message := get_next_message() process_message(message) while message != quit
多くのプログラムは、高レベル設計にイベントループ(メインイベントループ)を含んでいますが、代替設計も存在します。
プログラムは、外部(つまりユーザー)の介入なしに、設計された処理を完了するとすぐに終了できます。多くの場合、このようなプログラムには、コマンドラインインターフェース(CLI)や環境変数などを介して、プログラムの開始前に設定されるオプションの動作があります。ユーティリティソフトウェアは、しばしばこのような性質を持っています。
プログラムがユーザーとの対話機能を備えていても、必ずしもイベントループを使用するとは限りません。内部CLIを提供するプログラムには、ユーザー入力に基づいてコマンドハンドラにディスパッチを行うという点でイベントループに似たコマンドプロセッサが含まれています。
ウェブページとそのJavaScriptは、通常、シングルスレッドのウェブブラウザプロセスで実行されます。ブラウザプロセスは、キューからメッセージを1つずつ処理します。特定のメッセージには、JavaScript関数やその他のブラウザイベントが関連付けられている場合があります。ブラウザプロセスは、1つのメッセージの処理が完了すると、キュー内の次のメッセージに進みます。
JavaScript環境では、イベントループはタスクキューやマイクロタスクキューなどの個別のキューを使用して、タイマー、プロミスコールバック、DOMイベントなどの非同期操作を管理します。コールスタックが空になると、イベントループはこれらのキューから次のタスクを選択して実行します。[ 2 ]
Windowsでは、ユーザーと対話するプロセスは、受信メッセージを受け入れて応答する必要があります。これは、ほぼ必然的にそのプロセス内のメッセージ ループによって行われます。Windows では、メッセージはオペレーティングシステムに生成され、課せられるイベントに相当します。イベントには、ユーザー操作、ネットワーク トラフィック、システム処理、タイマー アクティビティ、プロセス間通信などがあります。対話型ではない、I/O のみのイベントについては、Windows にはI/O 完了ポートがあります。I/O 完了ポート ループはメッセージ ループとは別に実行され、デフォルトではメッセージ ループと相互作用しません。
ほとんどのWin32アプリケーションの中核はWinMain()関数であり、この関数はループ内でGetMessage()関数を呼び出します。GetMessage()はメッセージ(イベント)が受信されるまでブロックします(非ブロッキングの代替関数としてPeekMessage()関数も使用できます)。いくつかのオプション処理の後、 DispatchMessage()関数が呼び出され、メッセージが関連するハンドラ(WindowProcとも呼ばれます)にディスパッチされます。通常、特別なWindowProc()を持たないメッセージは、デフォルトのDefWindowProcにディスパッチされます。DispatchMessage()は、メッセージのHWNDハンドル( RegisterClass()関数で登録済み)のWindowProcを呼び出します。
Windows の最新バージョンでは、メッセージがシステムとその周辺機器によって認識された順序でアプリケーションのメッセージ ループに配信されることが保証されています。この保証は、マルチスレッドアプリケーションの設計上の影響を考慮する際に不可欠です。ただし、常に最後に受信されるメッセージや、文書化された優先度が異なるメッセージなど、一部のメッセージには異なるルールが適用されます。[ 3 ]
Xlibを直接使用するXアプリケーションはXNextEvent、関数群を中心に構築されています。XNextEventイベントがイベントキューに現れるまでブロックし、イベントが発生するとアプリケーションはそれを適切に処理します。Xlibイベントループはウィンドウシステムイベントのみを処理します。他のファイルやデバイスを待機する必要があるアプリケーションは、などのプリミティブから独自のイベントループを構築できますConnectionNumberが、実際にはマルチスレッドを使用する傾向があります。
Xlibを直接使用するプログラムはごくわずかです。より一般的なケースでは、XlibベースのGUIツールキットがイベントの追加をサポートしています。たとえば、Xt IntrinsicsXtAppAddInput()ベースのツールキットには、およびがありますXtAppAddTimeout()。
シグナルハンドラから Xlib 関数を呼び出すのは安全ではありません。X アプリケーションが、たとえば 内で任意の状態で中断されている可能性があるためですXNextEvent。X11R5、X11R6、Xt 向けのソリューション。
GLibイベント ループは元々GTKで使用するために作成されましたが、現在ではD-Busなどの非 GUI アプリケーションでも使用されています。ポーリングされるリソースは、アプリケーションが関心を持つファイル ディスクリプタの集合です。シグナルが到着するかタイムアウトが経過すると (たとえば、アプリケーションがタイムアウトまたはアイドル タスクを指定した場合)、ポーリング ブロックは中断されます。GLib にはファイル ディスクリプタと子プロセスの終了イベントに対する組み込みのサポートがありますが、準備-チェック-ディスパッチ モデルで処理できる任意のイベントに対してイベント ソースを追加することも可能です。
GLibイベントループに基づいて構築されたアプリケーションライブラリには、GStreamerやGnomeVFSの非同期I/Oメソッドなどがありますが、最もよく知られているクライアントライブラリはGTKです。ウィンドウシステム(XではXソケットから読み取られる)からのイベントは、 GDKによってGTKイベントに変換され、アプリケーションのウィジェットオブジェクト上でGLibシグナルとして発信されます。
スレッドごとに許可されるCFRunLoopは1つだけであり、ソースとオブザーバーは任意数だけ接続できます。ソースはランループを介してオブザーバーと通信し、ランループがメッセージのキューイングとディスパッチを管理します。
CFRunLoop はCocoaではNSRunLoop として抽象化されており、これにより、任意のメッセージ (非リフレクティブなランタイムにおける関数呼び出しに相当) をキューに入れて任意のオブジェクトにディスパッチすることができます。
Unixでは、「すべてはファイルである」というパラダイムにより、ファイルベースのイベントループが実現されます。ファイルの読み書き、プロセス間通信、ネットワーク通信、デバイス制御はすべてファイルI/Oを使用して行われ、ターゲットはファイルディスクリプタによって識別されます。selectおよびpollシステムコールを使用すると、一連のファイルディスクリプタの状態変化(例えば、読み取り可能なデータが利用可能になったときなど)を監視できます。
例えば、継続的に更新されるファイルから読み込み、その内容をX Window Systemに表示するプログラムを考えてみましょう。このプログラムはソケット(UnixドメインまたはBerkeley)を介してクライアントと通信します。
def main (): file_fd = open ( "logfile.log" ) x_fd = open_display () construct_interface () while True : rlist , _ , _ = select.select ([ file_fd , x_fd ], [ ] , []): if file_fd in rlist : data = file_fd.read ( ) append_to_display ( data ) send_repaint_message () if x_fd in rlist : process_x_messages ( )Unixでは、シグナルは非同期イベントであり、シグナルハンドラによって処理されます。シグナルハンドラは、タスクの残りの部分が中断されている間に実行されます。タスクがブロックされている間にシグナルが受信され処理された場合、selectはEINTRselect()で早期に戻ります。タスクがCPUバウンド中にシグナルが受信された場合、シグナルハンドラが戻るまで、タスクは命令間で中断されます。
シグナルを処理する方法の一つとして、シグナルハンドラがグローバルフラグを設定し、イベントループがselect()呼び出しの直前と直後にそのフラグをチェックするという方法があります。フラグが設定されている場合は、ファイルディスクリプタのイベントと同様の方法でシグナルを処理します。しかし、この方法では競合状態が発生します。フラグのチェックと呼び出しの間にシグナルが到着した場合、何らかの別の理由(例えば、イライラしたユーザーによる中断)でが戻るselect()まで、シグナルは処理されません。select()
POSIXがたどり着いた解決策はpselect()、 に似ているが、シグナル マスクを記述するselect()追加のパラメータを受け取るという呼び出しです。これにより、アプリケーションはメイン タスクでシグナルをマスクし、呼び出しの期間中はマスクを解除して、アプリケーションがI/O バウンドしている間だけシグナル ハンドラが呼び出されるようにすることができます。ただし、 の実装は必ずしも信頼できるものではありませんでした。Linux のバージョン 2.6.16 より前のバージョンにはシステム コールがなく、[ 4 ] glibcに、 が回避しようとしているのと同じ競合状態になりやすい方法でそれをエミュレートするように強制しています。sigmaskselect()pselect()pselect()pselect()
代替案として、より移植性の高い解決策として、自己パイプトリック[ 5 ]を使用して非同期イベントをファイルベースのイベントに変換する方法があります。これは、「シグナルハンドラがパイプにバイトを書き込み、そのパイプのもう一方の端はselect()メインプログラムで監視される」というものです[ 6 ] 。Linuxカーネルバージョン2.6.22では、特別なファイルディスクリプタを介してシグナルを受信できる新しいシステムコールsignalfd()が追加されました。
ウェブブラウザやNode.jsなどの環境では、イベントループは非同期プログラムの動作の中心となります。JavaScriptは実行時に単一のスレッドで動作しますが、ホスト環境はタイマー、ネットワーク要求、ユーザーイベントなどの非ブロッキング操作のためのAPIを提供します。完了した非同期操作はキューに入れられ、イベントループはコールスタックが空かどうかを繰り返しチェックして、キューに入れられたコールバックをスケジュールします。これにより、JavaScriptアプリケーションは複数の非同期タスクを処理しながら応答性を維持できます。[ 7 ]