อัปเดต 10/9/2013:ดูการแสดงภาพแบบโต้ตอบของ run loop: https://machty.s3.amazonaws.com/ember-run-loop-visual/index.html
อัปเดต 5/9/2013:แนวคิดพื้นฐานทั้งหมดด้านล่างยังคงเป็นข้อมูลล่าสุด แต่ในขณะนี้การใช้งาน Ember Run Loop ได้ถูกแยกออกเป็นไลบรารีแยกต่างหากที่เรียกว่าbackburner.jsโดยมีความแตกต่างเล็กน้อยของ API
ก่อนอื่นอ่านสิ่งเหล่านี้:
http://blog.sproutcore.com/the-run-loop-part-1/
http://blog.sproutcore.com/the-run-loop-part-2/
พวกเขาไม่ได้ถูกต้อง 100% สำหรับ Ember แต่แนวคิดหลักและแรงจูงใจที่อยู่เบื้องหลัง RunLoop โดยทั่วไปยังคงใช้กับ Ember รายละเอียดการใช้งานบางส่วนเท่านั้นที่แตกต่างกัน แต่สำหรับคำถามของคุณ:
Ember RunLoop เริ่มเมื่อใด ขึ้นอยู่กับเราเตอร์หรือมุมมองหรือคอนโทรลเลอร์หรืออย่างอื่นหรือไม่?
เหตุการณ์พื้นฐานทั้งหมดของผู้ใช้ (เช่นเหตุการณ์คีย์บอร์ดเหตุการณ์เมาส์ ฯลฯ ) จะเริ่มการทำงานของลูป สิ่งนี้รับประกันได้ว่าการเปลี่ยนแปลงใด ๆ ที่เกิดขึ้นกับคุณสมบัติที่ถูกผูกไว้โดยเหตุการณ์ที่จับภาพ (เมาส์ / คีย์บอร์ด / ตัวจับเวลา / ฯลฯ ) จะถูกแพร่กระจายอย่างสมบูรณ์ตลอดทั้งระบบการผูกข้อมูลของ Ember ก่อนที่จะส่งคืนการควบคุมกลับสู่ระบบ ดังนั้นการเลื่อนเมาส์กดแป้นคลิกปุ่ม ฯลฯ ทั้งหมดจะเป็นการเปิดใช้งานลูป
ใช้เวลาประมาณเท่าไหร่ (ฉันรู้ว่ามันค่อนข้างโง่ที่จะถามและขึ้นอยู่กับหลาย ๆ สิ่ง แต่ฉันกำลังมองหาแนวคิดทั่วไปหรือบางทีอาจต้องใช้เวลาขั้นต่ำหรือสูงสุดในการรันลูป)
RunLoop จะไม่ติดตามเลยว่าต้องใช้เวลาเท่าใดในการเผยแพร่การเปลี่ยนแปลงทั้งหมดผ่านระบบจากนั้นจึงหยุด RunLoop หลังจากถึงขีด จำกัด เวลาสูงสุด แต่ RunLoop จะทำงานจนเสร็จสิ้นเสมอและจะไม่หยุดจนกว่าจะมีการเรียกตัวจับเวลาที่หมดอายุทั้งหมดการเชื่อมโยงจะแพร่กระจายและบางทีการผูกของมันจะแพร่กระจายไปเรื่อย ๆ เห็นได้ชัดว่ายิ่งต้องเผยแพร่การเปลี่ยนแปลงจากเหตุการณ์เดียวมากเท่าไหร่ RunLoop ก็จะใช้เวลานานขึ้นเท่านั้น นี่คือตัวอย่าง (ค่อนข้างไม่ยุติธรรม) ของวิธีที่ RunLoop สามารถจมอยู่กับการเปลี่ยนแปลงการเผยแพร่เมื่อเทียบกับกรอบงานอื่น (Backbone) ที่ไม่มีการวนรอบการทำงาน: http://jsfiddle.net/jashkenas/CGSd5/. คุณธรรมของเรื่องราว: RunLoop เร็วมากสำหรับสิ่งต่างๆส่วนใหญ่ที่คุณเคยต้องการทำใน Ember และเป็นจุดที่พลังของ Ember อยู่มาก แต่ถ้าคุณพบว่าตัวเองต้องการทำให้วงกลม 30 วงเคลื่อนไหวด้วย Javascript ที่ 60 เฟรมต่อวินาทีที่นั่น อาจเป็นวิธีที่ดีกว่าในการใช้ RunLoop ของ Ember
RunLoop ถูกเรียกใช้งานตลอดเวลาหรือเป็นเพียงการระบุช่วงเวลาตั้งแต่ต้นจนจบการดำเนินการและอาจไม่ทำงานในบางครั้ง
มันไม่ได้ดำเนินการตลอดเวลา - ต้องคืนการควบคุมกลับสู่ระบบ ณ จุดใดจุดหนึ่งมิฉะนั้นแอปของคุณจะหยุดทำงานซึ่งแตกต่างจากการพูดวนรอบบนเซิร์ฟเวอร์ที่มีwhile(true)
และดำเนินต่อไปเป็นระยะอนันต์จนกระทั่ง เซิร์ฟเวอร์ได้รับสัญญาณให้ปิดตัวลง ... Ember RunLoop ไม่มีwhile(true)
แต่จะหมุนขึ้นเพื่อตอบสนองต่อเหตุการณ์ของผู้ใช้ / ตัวจับเวลาเท่านั้น
หากมุมมองถูกสร้างขึ้นจากภายใน RunLoop เดียวจะรับประกันได้หรือไม่ว่าเนื้อหาทั้งหมดจะเข้าสู่ DOM เมื่อการวนซ้ำสิ้นสุดลง
ลองดูว่าเราจะคิดออกไหม การเปลี่ยนแปลงครั้งใหญ่อย่างหนึ่งจาก SC เป็น Ember RunLoop คือแทนที่จะวนไปมาระหว่างinvokeOnce
และinvokeLast
(ซึ่งคุณเห็นในแผนผังในลิงค์แรกเกี่ยวกับ RL ของ SproutCore) Ember จะให้รายการ 'คิว' ที่อยู่ใน หลักสูตรของวง Run คุณสามารถกำหนดเวลาการดำเนินการ (ฟังก์ชั่นที่จะเรียกว่าในช่วงห่วงวิ่ง) ไปโดยระบุที่คิวการดำเนินการอยู่ใน (ตัวอย่างจากแหล่งที่มา: Ember.run.scheduleOnce('render', bindView, 'rerender');
)
หากคุณดูrun_loop.js
ในซอร์สโค้ดคุณจะเห็นEmber.run.queues = ['sync', 'actions', 'destroy', 'timers'];
แต่ถ้าคุณเปิดตัวดีบัก JavaScript ในเบราว์เซอร์ในแอป Ember และประเมินEmber.run.queues
คุณจะได้รับรายการคิวที่["sync", "actions", "render", "afterRender", "destroy", "timers"]
ครบถ้วนกว่า: Ember เก็บโค้ดเบสแบบแยกส่วนและทำให้โค้ดของคุณเป็นไปได้เช่นเดียวกับโค้ดของตัวเองในส่วนที่แยกต่างหากของไลบรารีเพื่อแทรกคิวเพิ่มเติม ในกรณีนี้ไลบรารี Ember Views จะแทรกrender
และafterRender
คิวโดยเฉพาะหลังactions
คิว ฉันจะไปถึงสาเหตุนั้นในไม่กี่วินาที ขั้นแรกอัลกอริทึม RunLoop:
อัลกอริทึม RunLoop ค่อนข้างเหมือนกับที่อธิบายไว้ในบทความ SC run loop ด้านบน:
- คุณรันโค้ดของคุณระหว่าง RunLoop
.begin()
และ.end()
เฉพาะใน Ember เท่านั้นที่คุณจะต้องการรันโค้ดของคุณภายในEmber.run
แทนซึ่งจะเรียกใช้ภายในbegin
และend
สำหรับคุณ (เฉพาะรหัสวนรอบการรันภายในในฐานรหัส Ember เท่านั้นที่ยังคงใช้begin
และend
คุณควรยึดติดไว้ด้วยEmber.run
)
- หลังจาก
end()
ถูกเรียกแล้ว RunLoop จะเข้าสู่เกียร์เพื่อเผยแพร่ทุกการเปลี่ยนแปลงที่เกิดขึ้นโดยกลุ่มของรหัสที่ส่งผ่านไปยังEmber.run
ฟังก์ชัน ซึ่งรวมถึงการเผยแพร่ค่าของคุณสมบัติที่ถูกผูกไว้การแสดงผลการเปลี่ยนแปลงมุมมองไปยัง DOM เป็นต้นลำดับที่ดำเนินการเหล่านี้ (การเชื่อมโยงการแสดงผลองค์ประกอบ DOM ฯลฯ ) ถูกกำหนดโดยEmber.run.queues
อาร์เรย์ที่อธิบายข้างต้น
sync
ห่วงการทำงานจะเริ่มออกคิวแรกซึ่งเป็น มันจะเรียกใช้การดำเนินการทั้งหมดที่กำหนดไว้ในsync
คิวด้วยEmber.run
รหัส การดำเนินการเหล่านี้อาจกำหนดเวลาการดำเนินการเพิ่มเติมในระหว่าง RunLoop เดียวกันนี้ด้วยและขึ้นอยู่กับ RunLoop เพื่อให้แน่ใจว่าจะดำเนินการทุกอย่างจนกว่าคิวทั้งหมดจะถูกล้าง วิธีดำเนินการนี้คือในตอนท้ายของทุกคิว RunLoop จะตรวจสอบคิวที่ล้างก่อนหน้านี้ทั้งหมดและดูว่ามีกำหนดการดำเนินการใหม่หรือไม่ ถ้าเป็นเช่นนั้นจะต้องเริ่มต้นที่จุดเริ่มต้นของคิวแรกสุดโดยมีการดำเนินการตามกำหนดเวลาที่ไม่สมบูรณ์และล้างคิวออกติดตามขั้นตอนต่อไปและเริ่มต้นใหม่เมื่อจำเป็นจนกว่าคิวทั้งหมดจะว่างเปล่าทั้งหมด
นั่นคือสาระสำคัญของอัลกอริทึม นั่นคือวิธีที่ข้อมูลที่ถูกผูกไว้แพร่กระจายผ่านแอพ คุณสามารถคาดหวังได้ว่าเมื่อ RunLoop ทำงานจนเสร็จข้อมูลที่ถูกผูกไว้ทั้งหมดจะได้รับการเผยแพร่อย่างสมบูรณ์ แล้วองค์ประกอบ DOM ล่ะ?
ลำดับของคิวรวมถึงคิวที่เพิ่มโดยไลบรารี Ember Views มีความสำคัญที่นี่ สังเกตว่าrender
และafterRender
หลังจากsync
นั้นและaction
. sync
คิวมีการดำเนินการทั้งหมดสำหรับการแพร่กระจายข้อมูลที่ถูกผูก ( action
หลังจากนั้นใช้เพียงเบาบางในแหล่ง Ember) ตามอัลกอริทึมข้างต้นรับประกันได้ว่าเมื่อ RunLoop เข้าสู่render
คิวการเชื่อมโยงข้อมูลทั้งหมดจะเสร็จสิ้นการซิงโครไนซ์ นี่คือการออกแบบ: คุณไม่ต้องการทำงานที่มีราคาแพงในการแสดงผลองค์ประกอบ DOMมาก่อนการซิงค์การเชื่อมโยงข้อมูลเนื่องจากอาจต้องใช้การแสดงผลองค์ประกอบ DOM ด้วยข้อมูลที่อัปเดตซึ่งเห็นได้ชัดว่าเป็นวิธีที่ไม่มีประสิทธิภาพและเกิดข้อผิดพลาดในการล้างคิว RunLoop ทั้งหมด ดังนั้น Ember จึงทำลายงานผูกข้อมูลทั้งหมดที่ทำได้อย่างชาญฉลาดก่อนที่จะแสดงผลองค์ประกอบ DOM ในrender
คิว
ในที่สุดเพื่อตอบคำถามของคุณใช่คุณสามารถคาดหวังได้ว่าการเรนเดอร์ DOM ที่จำเป็นจะเกิดขึ้นเมื่อเวลาEmber.run
เสร็จสิ้น นี่คือ jsFiddle ที่จะสาธิต: http://jsfiddle.net/machty/6p6XJ/328/
สิ่งอื่น ๆ ที่ควรทราบเกี่ยวกับ RunLoop
ผู้สังเกตการณ์เทียบกับการผูกมัด
สิ่งสำคัญคือต้องสังเกตว่าผู้สังเกตการณ์และการเชื่อมโยงในขณะที่มีฟังก์ชันการทำงานที่คล้ายคลึงกันในการตอบสนองต่อการเปลี่ยนแปลงในคุณสมบัติ "เฝ้าดู" จะทำงานแตกต่างกันโดยสิ้นเชิงในบริบทของ RunLoop การขยายพันธุ์ที่มีผลผูกพันตามที่เราเห็นจะถูกกำหนดไว้ในsync
คิวเพื่อให้ RunLoop ดำเนินการในที่สุด ในทางกลับกันผู้สังเกตการณ์จะเริ่มทำงานทันทีเมื่อคุณสมบัติที่เฝ้าดูเปลี่ยนไปโดยไม่ต้องกำหนดเวลาไว้ในคิว RunLoop ก่อน หากผู้สังเกตการณ์และ "นาฬิกา" ที่มีผลผูกพันทั้งหมดมีคุณสมบัติเดียวกันผู้สังเกตการณ์จะถูกเรียก 100% ก่อนเวลาที่การเชื่อมโยงจะได้รับการอัปเดตเสมอ
scheduleOnce
และ Ember.run.once
หนึ่งในประสิทธิภาพที่เพิ่มขึ้นอย่างมากในเทมเพลตการอัปเดตอัตโนมัติของ Ember นั้นขึ้นอยู่กับข้อเท็จจริงที่ว่าด้วย RunLoop การดำเนินการ RunLoop ที่เหมือนกันหลายรายการสามารถรวมกัน ("debounce" หากคุณต้องการ) เป็นแอ็คชันเดียว หากคุณมองเข้าไปในrun_loop.js
internals คุณจะเห็นฟังก์ชั่นที่อำนวยความสะดวกในการทำงานนี้เป็นฟังก์ชั่นที่เกี่ยวข้องและscheduleOnce
Em.run.once
ความแตกต่างระหว่างพวกเขาไม่สำคัญเท่าการรู้ว่ามีอยู่จริงและวิธีที่พวกเขาสามารถละทิ้งการกระทำที่ซ้ำกันในคิวเพื่อป้องกันการคำนวณที่ป่องและสิ้นเปลืองระหว่างลูปการรัน
แล้วตัวจับเวลาล่ะ?
แม้ว่า 'ตัวจับเวลา' จะเป็นหนึ่งในคิวเริ่มต้นที่ระบุไว้ข้างต้น แต่ Ember จะอ้างอิงเฉพาะคิวในกรณีทดสอบ RunLoop เท่านั้น ดูเหมือนว่าคิวดังกล่าวจะถูกใช้ในวัน SproutCore ตามคำอธิบายบางส่วนจากบทความด้านบนเกี่ยวกับตัวจับเวลาเป็นสิ่งสุดท้ายที่จะเริ่มทำงาน ใน Ember timers
ไม่ได้ใช้คิว RunLoop สามารถหมุนได้โดยsetTimeout
เหตุการณ์ที่จัดการภายใน(ดูinvokeLaterTimers
ฟังก์ชัน) ซึ่งฉลาดพอที่จะวนซ้ำตัวจับเวลาที่มีอยู่ทั้งหมดเริ่มการทำงานของตัวจับเวลาทั้งหมดที่หมดอายุกำหนดตัวจับเวลาในอนาคตที่เร็วที่สุดและตั้งค่าภายในsetTimeout
สำหรับเหตุการณ์นั้นเท่านั้นซึ่งจะหมุน RunLoop อีกครั้งเมื่อเริ่มทำงาน วิธีนี้มีประสิทธิภาพมากกว่าการตั้งเวลาโทรแต่ละครั้ง setTimeout และปลุกตัวเองเนื่องจากในกรณีนี้ต้องทำการโทร setTimeout เพียงชุดเดียวและ RunLoop นั้นฉลาดพอที่จะเริ่มการทำงานของตัวจับเวลาที่แตกต่างกันทั้งหมดที่อาจจะดับลงพร้อมกัน เวลา.
แก้ไขเพิ่มเติมด้วยsync
คิว
นี่คือตัวอย่างข้อมูลจาก run loop ที่อยู่ตรงกลางของลูปผ่านคิวทั้งหมดใน run loop หมายเหตุกรณีพิเศษสำหรับ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);
}
หวังว่านี่จะช่วยได้ แน่นอนว่าฉันต้องเรียนรู้ไม่น้อยเพียงแค่เขียนสิ่งนี้ซึ่งเป็นประเด็น