1. ホーム
  2. ember.js

[解決済み] Ember RunLoopとは何か、どのように機能するのか?

2023-01-28 18:26:49

質問

EmberのRunLoopがどのように動作し、何がそれを動かすのかを理解しようとしています。私は見てきました ドキュメント を見ましたが、まだそれについて多くの質問があります。私は、いくつかのコードの実行を後で延期しなければならないときに、その名前空間内で適切なメソッドを選択できるように、RunLoopがどのように動作するかをよりよく理解することに興味があります。

  • Ember RunLoop はいつ始まるのでしょうか。Router や Views、Controller などに依存するのでしょうか。
  • どのくらいの時間がかかりますか (私はこれを尋ねることはむしろ愚かであり、多くのものに依存していることを知っているが、私は一般的なアイデアを探しています、または多分ランループが取るかもしれない最小または最大の時間がある場合)
  • RunLoop は常に実行されているのですか、それとも実行の開始から終了までの期間を示しているだけで、しばらくは実行されないかもしれません。
  • 1つのRunLoop内からビューが作成された場合、ループが終了するまでにそのすべてのコンテンツがDOMに入ることは保証されていますか?

これらが非常に基本的な質問である場合、私はこれらを理解することが私のようなnoobがより良いEmberを使用するのに役立つと思います。

どのように解決するのですか?

2013年10月9日に更新しました。 ランループのインタラクティブなビジュアライゼーションをご覧ください。 https://machty.s3.amazonaws.com/ember-run-loop-visual/index.html

2013年5月9日に更新しました。 以下の基本コンセプトはすべて最新のものですが、現在では このコミット という別のライブラリに分割され、Ember Run Loop の実装は削除されました。 backburner.js という別のライブラリに分割され、ごくわずかなAPIの違いがあります。

まず最初に、これらを読んでください。

http://blog.sproutcore.com/the-run-loop-part-1/

http://blog.sproutcore.com/the-run-loop-part-2/

Emberに100%正確というわけではありませんが、RunLoopのコアとなるコンセプトと動機はEmberにも概ね当てはまり、いくつかの実装の詳細が異なるだけです。しかし、あなたの質問についてです。

Ember RunLoopはいつ始まるのでしょうか。RouterやViews、Controllerなどに依存するのでしょうか?

基本的なユーザーイベント (キーボードイベント、マウスイベントなど) はすべて、ランループを起動します。これは、キャプチャされた (マウス/キーボード/タイマーなど) イベントによってバインドされたプロパティに加えられたいかなる変更も、システムに制御を戻す前に Ember のデータ バインディング システム全体に完全に伝搬されることを保証します。つまり、マウスを動かす、キーを押す、ボタンをクリックするなど、すべて実行ループを起動します。

それはおよそどれくらいかかりますか (私はこれが尋ねるにはかなり愚かで、多くのものに依存していることを知っていますが、私は一般的なアイデアを探しています、または多分、ランループが取るかもしれない最小または最大の時間がある場合)

むしろ、RunLoop は常に完了するまで実行され、期限切れのタイマーがすべて呼び出され、バインディングが伝搬され、そしておそらくは その バインディングが伝搬される、といった具合に。明らかに、1つのイベントから伝搬される必要がある変更が多ければ多いほど、RunLoopが終了するのに時間がかかります。以下は、ランループを持たない別のフレームワーク(Backbone)と比較して、ランループが変更の伝播でどのように泥沼にはまるかの(かなり不公平な)例です。 http://jsfiddle.net/jashkenas/CGSd5/ . しかし、Javascript で 30 個の円を 60 フレーム/秒でアニメーションさせたい場合、Ember の RunLoop に依存するよりも良い方法があるかもしれません。

RunLoopは常時実行されているのか、それとも実行開始から終了までの期間を示しているだけで、しばらくは実行されない可能性があるのか。

常に実行されているわけではありません。ある時点でシステムに制御を戻す必要があり、そうしないとアプリがハングアップします。 while(true) というように、サーバーがシャットダウンする信号を受け取るまで無限に続くのです。 while(true) がなく、ユーザーやタイマーのイベントに応じてのみ起動されます。

1つのRunLoop内からビューが作成された場合、ループが終了するまでにそのすべてのコンテンツがDOMに入ることが保証されていますか?

それがわかるかどうか見てみましょう。SCからEmber RunLoopへの大きな変更点の1つは、ループの中で invokeOnceinvokeLast (を使うことで、Emberは「キュー」のリストを提供し、実行ループの過程で、アクションがどのキューに属するかを指定して、アクション(実行ループ中に呼び出す関数)をスケジュールできます(ソースからの例です)。 Ember.run.scheduleOnce('render', bindView, 'rerender'); ).

を見ると run_loop.js を見ると、ソースコードに Ember.run.queues = ['sync', 'actions', 'destroy', 'timers']; と表示されますが、EmberアプリのブラウザでJavaScriptデバッガを開いて Ember.run.queues を評価すると、より詳細なキューのリストが表示されます。 ["sync", "actions", "render", "afterRender", "destroy", "timers"] . Ember はコードベースをかなりモジュール化しており、ライブラリの別の部分にある独自のコードだけでなく、あなたのコードもさらにキューを挿入できるようにしています。この場合、Ember Views ライブラリでは renderafterRender キュー、特に actions キューの後にあります。なぜそうなるかは、後ほど説明します。まず、RunLoopアルゴリズムです。

RunLoopのアルゴリズムは、上記のSCランループの記事で説明したものとほとんど同じです。

  • RunLoop の間でコードを実行する .begin().end() の中でコードを実行することになります。 Ember.run の中でコードを実行することになります。 beginend を追加しました。(Emberコードベースの内部ランループのコードのみ、まだ beginend を使うべきで、そのためには Ember.run )
  • その後 end() が呼び出された後、RunLoop が起動し Ember.run 関数に渡されたコードのチャンクによって行われたあらゆる変更を伝播するためにギアを上げます。これには、バインドされたプロパティの値の伝搬、DOM へのビュー変更のレンダリングなどが含まれます。これらのアクション(バインド、DOM 要素のレンダリングなど)が実行される順序は Ember.run.queues 配列によって決定されます。
  • 実行ループは最初のキューで開始され、それは sync . にスケジュールされたすべてのアクションを実行します。 sync キューにスケジュールされたすべてのアクションを実行します。 Ember.run のコードによって実行される。これらのアクションは、それ自体が、この同じRunLoopの間に実行されるより多くのアクションをスケジュールすることもでき、すべてのキューがフラッシュされるまですべてのアクションを実行することを確認するのは、RunLoop次第です。これを行う方法は、各キューの終わりに、RunLoopは以前にフラッシュされたすべてのキューに目を通し、新しいアクションがスケジュールされているかどうかを確認することです。もしそうなら、それは、実行されていないスケジュールされたアクションを持つ最も古いキューの先頭から始まり、キューを洗い流し、そのステップをトレースし続け、すべてのキューが完全に空になるまで、必要に応じてやり直さなければなりません。

これがアルゴリズムのエッセンスです。これが、バインドされたデータがアプリを通じて伝搬される方法です。RunLoopが完了するまで実行されると、すべてのバインドデータが完全に伝搬されることが期待できます。では、DOM要素についてはどうでしょうか。

Ember Views ライブラリによって追加されたキューを含め、キューの順序はここで重要です。注目すべきは renderafterRender の次に来る sync であり、かつ action . は sync キューには、バインドされたデータを伝搬するためのすべてのアクションが含まれています。( action は、その後、Ember のソースではまばらにしか使用されていません)。上記のアルゴリズムに基づくと、RunLoop が render キューに到達するまでに、すべてのデータバインディングの同期が終了していることが保証されています。これは設計上、DOM 要素のレンダリングという高価なタスクを実行したくありません。 の前に を同期する前に、DOM 要素を レンダリングするという高価なタスクを実行したくないでしょう。そのためには、おそらく更新されたデータで DOM 要素を再レンダリングする必要があり、明らかに非常に非効率でエラーが起こりやすい方法で、すべての RunLoop キューを空にします。そのため、Ember は、データバインディングの作業をできる限りすべて行ってから、DOM 要素を render キューに入れます。

最後に、あなたの質問に答えますと、はい、必要な DOM レンダリングは Ember.run が終了するまでに、必要な DOM レンダリングが行われると予想できます。以下は、jsFiddleによるデモです。 http://jsfiddle.net/machty/6p6XJ/328/

その他、RunLoopについて知っておくべきこと

オブザーバとバインディングの比較

Observers と Bindings は、"watched" プロパティの変更に応答するという同様の機能を持ちながら、RunLoop のコンテキストでは全く異なる動作をすることに注意することが重要です。バインディングのプロパゲーションは、これまで見てきたように、スケジュール化され sync キューにスケジュールされ、最終的に RunLoop によって実行される。一方、オブザーバは を即座に 一方、オブザーバーは、ウォッチされたプロパティが変更されたときに、最初にRunLoopのキューにスケジュールされることなく、直ちに発火します。Observer とバインディングがすべて同じプロパティを監視する場合、Observer はバインディングが更新されるよりも常に 100% 早く呼ばれることになります。

scheduleOnceEmber.run.once

Ember の自動更新テンプレートにおける大きな効率化のひとつは、RunLoop のおかげで、複数の同一の RunLoop アクションを単一のアクションに合体させることができる ("debounced" とでも言いましょうか) という事実に基づいています。もしあなたが run_loop.js 内部を見ると、この動作を促進する関数として、関連する関数 scheduleOnceEm.run.once . これらの違いは、これらの存在を知ること、そして、実行ループ中に多くの肥大化した無駄な計算を防ぐために、キュー内の重複するアクションをどのように破棄することができるかと同じくらい重要ではありません。

タイマーについてはどうですか?

timers」は上記のデフォルトキューの1つであるにもかかわらず、EmberはRunLoopテストケースの中でしかこのキューを参照していません。上記の記事の中で、タイマーが最後に発火するという記述があることから、SproutCore時代にはこのようなキューが使われていたようです。Emberでは timers キューは使用されません。その代わり、RunLoopは内部で管理された setTimeout イベント ( invokeLaterTimers 関数を参照)、このイベントは既存のタイマーを全てループし、期限が切れたタイマーを全て起動し、最も早い将来のタイマーを決定し、内部の setTimeout を設定し、そのイベントが発生したときに再びRunLoopをスピンアップさせます。この場合、1 つの setTimeout 呼び出しを行うだけでよく、RunLoop は同時にオフになる可能性があるすべての異なるタイマーを起動できるほど賢明であるため、この方法は各タイマーに setTimeout を呼び出して自身を起動させるよりも効率的です。

さらにデバウンスするために sync キュー

以下は、実行ループの中のすべてのキューを通過するループの途中のスニペットです。の特殊なケースに注意してください。 sync キューが特殊であることに注意してください。 sync は特に揮発性の高いキューで、データがあらゆる方向に伝搬されるからです。 Ember.beginPropertyChanges() が呼び出され、オブザーバが起動されないようにし、その後に Ember.endPropertyChanges . これは賢明な方法です。 sync キューをフラッシュする過程で、オブジェクトのプロパティが最終的な値に落ち着くまでに何度も変更される可能性があり、変更ごとにオブザーバを即座に起動してリソースを無駄にしたくありません。

if (queueName === 'sync') 
{
    log = Ember.LOG_BINDINGS;

    if (log) 
    {
        Ember.Logger.log('Begin: Flush Sync Queue');
    }

    Ember.beginPropertyChanges();
    Ember.tryFinally(tryable, Ember.endPropertyChanges);

    if (log) 
    { 
        Ember.Logger.log('End: Flush Sync Queue'); 
    }
} 
else 
{
   forEach.call(queue, iter);
}

これが役に立つといいのですが。私はこれを書くためにかなりのことを学ばなければなりませんでしたが、それは一種のポイントでした。