Ember RunLoop คืออะไรและทำงานอย่างไร?


96

ฉันพยายามทำความเข้าใจว่า Ember RunLoop ทำงานอย่างไรและอะไรทำให้มันถูกต้อง ฉันได้ดูเอกสารแล้ว แต่ยังมีคำถามมากมายเกี่ยวกับเรื่องนี้ ฉันสนใจที่จะทำความเข้าใจให้ดีขึ้นว่า RunLoop ทำงานอย่างไรดังนั้นฉันจึงสามารถเลือกวิธีการที่เหมาะสมภายในพื้นที่ชื่อเมื่อฉันต้องเลื่อนการเรียกใช้โค้ดบางส่วนออกไปในภายหลัง

  • Ember RunLoop เริ่มเมื่อใด ขึ้นอยู่กับเราเตอร์หรือมุมมองหรือคอนโทรลเลอร์หรืออย่างอื่นหรือไม่?
  • ใช้เวลาประมาณเท่าไหร่ (ฉันรู้ว่ามันค่อนข้างโง่ที่จะถามและขึ้นอยู่กับหลาย ๆ สิ่ง แต่ฉันกำลังมองหาแนวคิดทั่วไปหรือบางทีอาจต้องใช้เวลาขั้นต่ำหรือสูงสุดในการรันลูป)
  • RunLoop ถูกเรียกใช้งานตลอดเวลาหรือเป็นเพียงการระบุช่วงเวลาตั้งแต่ต้นจนจบการดำเนินการและอาจไม่ทำงานในบางครั้ง
  • หากมุมมองถูกสร้างขึ้นจากภายใน RunLoop เดียวจะรับประกันได้หรือไม่ว่าเนื้อหาทั้งหมดจะเข้าสู่ DOM เมื่อการวนซ้ำสิ้นสุดลง

ยกโทษให้ฉันหากคำถามเหล่านี้เป็นคำถามพื้นฐานฉันคิดว่าการเข้าใจสิ่งเหล่านี้จะช่วยให้ noobs อย่างฉันใช้ Ember ได้ดีขึ้น


5
ไม่มีเอกสารที่ยอดเยี่ยมเกี่ยวกับ run loop ฉันจะพยายามรวบรวมสไลด์เดอร์สั้น ๆ ในสัปดาห์นี้
Luke Melia

2
@LukeMelia คำถามนี้ยังคงต้องการความสนใจจากคุณเป็นอย่างยิ่งและดูเหมือนว่ามีคนอื่นกำลังมองหาข้อมูลเดียวกัน คงจะดีมากถ้าคุณมีโอกาสแบ่งปันข้อมูลเชิงลึกเกี่ยวกับ RunLoop
อารา

คำตอบ:


199

อัปเดต 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.jsinternals คุณจะเห็นฟังก์ชั่นที่อำนวยความสะดวกในการทำงานนี้เป็นฟังก์ชั่นที่เกี่ยวข้องและ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);
}

หวังว่านี่จะช่วยได้ แน่นอนว่าฉันต้องเรียนรู้ไม่น้อยเพียงแค่เขียนสิ่งนี้ซึ่งเป็นประเด็น


3
เขียนดีมาก! ฉันได้ยินข่าวลือว่า "ผู้สังเกตการณ์ยิงทันที" อาจมีการเปลี่ยนแปลงในบางจุดทำให้ล่าช้าเหมือนการผูกมัด
Jo Liss

@JoLiss ใช่ฉันรู้สึกเหมือนเคยได้ยินเรื่องนั้นมาสองสามเดือนแล้ว ... ไม่แน่ใจว่าจะเข้ามาเมื่อไหร่
Alexander Wallace Matchneer

1
Brendan Briggs ได้นำเสนอที่ยอดเยี่ยมเกี่ยวกับ Run Loop ในงานมีตติ้ง Ember.js NYC ในเดือนมกราคม 2014 วิดีโอที่นี่: youtube.com/watch?v=iCZUKFNXA0k
Luke Melia

1
คำตอบนี้เป็นแหล่งข้อมูลที่ดีที่สุดที่ฉันพบเกี่ยวกับ Ember Run Loop ดีมาก! ฉันเพิ่งเผยแพร่บทช่วยสอนมากมายเกี่ยวกับ Run Loop ตามงานของคุณซึ่งฉันหวังว่าจะอธิบายรายละเอียดเพิ่มเติมของกลไกนั้น ดูได้ที่นี่on.netguru.co/ember-ebook-form
Kuba Niechciał
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.