เหตุใด Subjects จึงไม่แนะนำใน. NET Reactive Extensions


111

ขณะนี้ฉันกำลังจับกับ Reactive Extensions framework สำหรับ. NET และฉันกำลังดำเนินการตามแหล่งข้อมูลแนะนำต่างๆที่ฉันพบ (ส่วนใหญ่เป็นhttp://www.introtorx.com )

แอปพลิเคชันของเราเกี่ยวข้องกับอินเทอร์เฟซฮาร์ดแวร์จำนวนมากที่ตรวจจับเฟรมเครือข่ายสิ่งเหล่านี้จะเป็น IObservables ของฉันจากนั้นฉันก็มีส่วนประกอบหลายอย่างที่จะใช้เฟรมเหล่านั้นหรือทำการแปลงข้อมูลบางอย่างและสร้างเฟรมประเภทใหม่ นอกจากนี้ยังมีส่วนประกอบอื่น ๆ ที่จำเป็นต้องแสดงทุก n เฟรมเช่น ฉันเชื่อว่า Rx จะเป็นประโยชน์สำหรับแอปพลิเคชันของเราอย่างไรก็ตามฉันกำลังดิ้นรนกับรายละเอียดการใช้งานสำหรับอินเทอร์เฟซ IObserver

ทรัพยากรส่วนใหญ่ (ถ้าไม่ใช่ทั้งหมด) ที่ฉันอ่านได้บอกว่าฉันไม่ควรใช้อินเทอร์เฟซ IObservable ด้วยตัวเอง แต่ใช้ฟังก์ชันหรือคลาสที่มีให้ จากการวิจัยของฉันดูเหมือนว่าการสร้างSubject<IBaseFrame>จะให้สิ่งที่ฉันต้องการฉันจะมีเธรดเดียวของฉันที่อ่านข้อมูลจากอินเทอร์เฟซฮาร์ดแวร์แล้วเรียกใช้ฟังก์ชัน OnNext ของSubject<IBaseFrame>อินสแตนซ์ของฉัน ส่วนประกอบ IObserver ที่แตกต่างกันจะได้รับการแจ้งเตือนจากเรื่องนั้น

ความสับสนของฉันมาจากคำแนะนำที่ให้ไว้ในภาคผนวกของบทช่วยสอนนี้ซึ่งระบุว่า:

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

แอปพลิเคชันของฉันค่อนข้างสำคัญในด้านประสิทธิภาพฉันกำลังจะทดสอบประสิทธิภาพของการใช้รูปแบบ Rx ก่อนที่จะเข้าสู่รหัสการผลิต อย่างไรก็ตามฉันกังวลว่าฉันกำลังทำบางสิ่งที่ขัดต่อเจตนารมณ์ของเฟรมเวิร์ก Rx โดยใช้คลาส Subject และเฟรมเวิร์กเวอร์ชันในอนาคตจะส่งผลกระทบต่อประสิทธิภาพ

มีวิธีที่ดีกว่าในการทำสิ่งที่ฉันต้องการหรือไม่? เธรดการสำรวจฮาร์ดแวร์จะทำงานอย่างต่อเนื่องไม่ว่าจะมีผู้สังเกตการณ์หรือไม่ก็ตาม (บัฟเฟอร์ HW จะสำรองข้อมูลเป็นอย่างอื่น) ดังนั้นนี่จึงเป็นลำดับที่ร้อนแรงมาก ฉันต้องส่งเฟรมที่ได้รับออกไปให้ผู้สังเกตการณ์หลายคน

คำแนะนำใด ๆ จะได้รับการชื่นชมอย่างมาก


1
มันช่วยให้ฉันเข้าใจเรื่องนี้มากฉันเพิ่งเข้าใจในหัวของฉันเกี่ยวกับวิธีใช้มันในแอปพลิเคชันของฉัน ฉันรู้ว่ามันเป็นสิ่งที่ถูกต้อง - ฉันมีไปป์ไลน์ของส่วนประกอบที่เน้นการผลักดันมากและฉันต้องทำการกรองทุกชนิดและเรียกใช้เธรด UI เพื่อแสดงใน GUI รวมถึงการบัฟเฟอร์เฟรมที่ได้รับล่าสุดเป็นต้น ฯลฯ - ฉันต้องแน่ใจว่าทำครั้งแรกถูกต้อง!
Anthony

คำตอบ:


70

โอเคถ้าเราเพิกเฉยต่อวิธีการดันทุรังของฉันและเพิกเฉย "เรื่องที่ดี / ไม่ดี" ทั้งหมด ให้เราดูพื้นที่ปัญหา

ฉันพนันได้เลยว่าคุณมีระบบ 1 จาก 2 รูปแบบที่คุณต้องใช้

  1. ระบบแจ้งเหตุการณ์หรือโทรกลับเมื่อมีข้อความเข้ามา
  2. คุณต้องสำรวจระบบเพื่อดูว่ามีข้อความที่ต้องดำเนินการหรือไม่

สำหรับตัวเลือกที่ 1 ง่าย ๆ เพียงแค่ห่อด้วยวิธี FromEvent ที่เหมาะสมเท่านี้ก็เสร็จแล้ว ไปผับ!

สำหรับตัวเลือกที่ 2 ตอนนี้เราต้องพิจารณาว่าเราสำรวจสิ่งนี้อย่างไรและจะทำอย่างไรให้มีประสิทธิภาพ เมื่อเราได้รับคุณค่าแล้วเราจะเผยแพร่อย่างไร?

ฉันคิดว่าคุณต้องการเธรดเฉพาะสำหรับการสำรวจความคิดเห็น คุณคงไม่ต้องการให้ coder ตัวอื่นตอก ThreadPool / TaskPool และปล่อยให้คุณอยู่ในสถานการณ์อดอยากของ ThreadPool หรือคุณไม่ต้องการความยุ่งยากในการสลับบริบท (ฉันเดา) สมมติว่าเรามีเธรดของเราเองเราอาจจะมี While / Sleep loop ที่เรานั่งในการสำรวจความคิดเห็น เมื่อตรวจสอบพบบางข้อความเราจะเผยแพร่ ทั้งหมดนี้ฟังดูสมบูรณ์แบบสำหรับ Observable.Create ตอนนี้เราอาจไม่สามารถใช้การวนซ้ำในขณะที่ไม่อนุญาตให้เราส่งคืน Disposable เพื่ออนุญาตการยกเลิก โชคดีที่คุณได้อ่านทั้งเล่มจึงเข้าใจด้วยการตั้งเวลาซ้ำ!

ฉันคิดว่าสิ่งนี้จะได้ผล #NotTested

public class MessageListener
{
    private readonly IObservable<IMessage> _messages;
    private readonly IScheduler _scheduler;

    public MessageListener()
    {
        _scheduler = new EventLoopScheduler();

        var messages = ListenToMessages()
                                    .SubscribeOn(_scheduler)
                                    .Publish();

        _messages = messages;
        messages.Connect();
    }

    public IObservable<IMessage> Messages
    {
        get {return _messages;}
    }

    private IObservable<IMessage> ListenToMessages()
    {
        return Observable.Create<IMessage>(o=>
        {
                return _scheduler.Schedule(recurse=>
                {
                    try
                    {           
                        var messages = GetMessages();
                        foreach (var msg in messages)
                        {
                            o.OnNext(msg);
                        }   
                        recurse();
                    }
                    catch (Exception ex)
                    {
                        o.OnError(ex);
                    }                   
                });
        });
    }

    private IEnumerable<IMessage> GetMessages()
    {
         //Do some work here that gets messages from a queue, 
         // file system, database or other system that cant push 
         // new data at us.
         // 
         //This may return an empty result when no new data is found.
    }
}

เหตุผลที่ฉันไม่ชอบ Subjects ก็คือโดยปกติแล้วเป็นกรณีของนักพัฒนาที่ไม่มีการออกแบบที่ชัดเจนเกี่ยวกับปัญหา แฮ็คในหัวเรื่องสะกิดมันที่นั่นและทุกที่แล้วปล่อยให้ผู้สนับสนุนที่ไม่ดีเดาว่า WTF กำลังเกิดขึ้น เมื่อคุณใช้เมธอดสร้าง / สร้าง ฯลฯ คุณกำลังโลคัลไลซ์เอฟเฟกต์บนลำดับ คุณสามารถดูทั้งหมดได้ในวิธีเดียวและคุณรู้ว่าไม่มีใครโยนผลข้างเคียงที่น่ารังเกียจ ถ้าฉันเห็นสาขาวิชาตอนนี้ฉันต้องค้นหาสถานที่ทั้งหมดในชั้นเรียนที่มีการใช้ หาก MFer บางคนเปิดเผยต่อสาธารณะการเดิมพันทั้งหมดจะปิดลงใครจะรู้ว่าลำดับนี้ถูกใช้อย่างไร! Async / Concurrency / Rx นั้นยาก คุณไม่จำเป็นต้องทำให้มันยากขึ้นโดยปล่อยให้ผลข้างเคียงและการเขียนโปรแกรมเชิงสาเหตุมาทำให้หัวของคุณหมุนมากขึ้น


10
ฉันเพิ่งอ่านคำตอบนี้ตอนนี้ แต่ฉันรู้สึกว่าฉันควรจะชี้ให้เห็นว่าฉันจะไม่พิจารณาที่จะเปิดเผยอินเทอร์เฟซหัวเรื่อง! ฉันกำลังใช้มันเพื่อจัดเตรียมการใช้งาน IObservable <> ภายในคลาสที่ปิดผนึก (ซึ่งจะเปิดเผย IObservable <>) ฉันเห็นได้ชัดว่าทำไมการเปิดเผยอินเทอร์เฟซ Subject <> จึงเป็นเรื่องที่ไม่ดี
Anthony

เดี๋ยวก่อนขอโทษด้วยนะ แต่ฉันไม่เข้าใจรหัสของคุณจริงๆ ListenToMessages () และ GetMessages () กำลังทำอะไรและส่งคืน
user10479

1
สำหรับโครงการส่วนตัวของคุณ @jeromerg นี่อาจจะดี อย่างไรก็ตามจากประสบการณ์ของฉันนักพัฒนาต่อสู้กับ WPF, MVVM, การออกแบบ GUI การทดสอบหน่วยและการโยน Rx สามารถทำให้สิ่งต่างๆซับซ้อนมากขึ้น ฉันได้ลองใช้รูปแบบ BehaviourSubject-as-a-property แล้ว อย่างไรก็ตามฉันพบว่ามันสามารถนำมาใช้สำหรับคนอื่น ๆ ได้มากขึ้นหากเราใช้คุณสมบัติ INPC มาตรฐานแล้วใช้วิธีการขยายอย่างง่ายเพื่อแปลงสิ่งนี้เป็น IObservable นอกจากนี้คุณจะต้องมีการผูก WPF แบบกำหนดเองเพื่อทำงานกับหัวข้อพฤติกรรมของคุณ ตอนนี้ทีมยากจนของคุณต้องเรียนรู้ WPF, MVVM, Rx และกรอบงานใหม่ของคุณด้วย
Lee Campbell

2
@LeeCampbell เพื่อใส่ไว้ในตัวอย่างโค้ดของคุณวิธีปกติคือ MessageListener ถูกสร้างขึ้นโดยระบบ (คุณอาจลงทะเบียนชื่อคลาสอย่างใดอย่างหนึ่ง) และคุณได้รับแจ้งว่าระบบจะเรียก OnCreate () และ OnGoodbye () และจะเรียก message1 (), message2 () และ message3 () เมื่อข้อความถูกสร้างขึ้น ดูเหมือนว่า messageX [123] จะเรียก OnNext ในหัวเรื่อง แต่มีวิธีที่ดีกว่านี้ไหม
James Moore

1
@JamesMoore เนื่องจากสิ่งเหล่านี้ง่ายต่อการอธิบายด้วยตัวอย่างที่เป็นรูปธรรม หากคุณรู้จักแอป Android แบบโอเพนซอร์สที่ใช้ Rx และ Subjects ฉันอาจหาเวลาดูว่าจะให้วิธีที่ดีกว่านี้ได้ไหม ฉันเข้าใจดีว่าการยืนบนแท่นและพูดว่า Subjects ไม่ดีนั้นมีประโยชน์ไม่มาก แต่ฉันคิดว่าสิ่งต่างๆเช่น IntroToRx, RxCookbook และ ReactiveTrader ล้วนให้ตัวอย่างวิธีการใช้ Rx ในระดับต่างๆ
Lee Campbell

38

โดยทั่วไปคุณควรหลีกเลี่ยงการใช้Subjectอย่างไรก็ตามสำหรับสิ่งที่คุณกำลังทำอยู่ที่นี่ฉันคิดว่ามันใช้ได้ดีทีเดียว ฉันถามคำถามที่คล้ายกันเมื่อพบข้อความ "หลีกเลี่ยงเรื่อง" ในบทแนะนำ Rx

อ้างถึงDave Sexton (จาก Rxx)

"Subjects เป็นส่วนประกอบที่เป็นสถานะของ Rx ซึ่งมีประโยชน์เมื่อคุณต้องการสร้างสิ่งที่สังเกตได้ในลักษณะเหตุการณ์เป็นฟิลด์หรือตัวแปรในเครื่อง"

ฉันมักจะใช้เป็นจุดเข้าสู่ Rx ดังนั้นถ้าฉันมีโค้ดบางส่วนที่ต้องบอกว่า 'สิ่งที่เกิดขึ้น (เช่นคุณมี) ผมจะใช้และโทรSubject OnNextจากนั้นเปิดเผยสิ่งนั้นIObservableเพื่อให้ผู้อื่นสมัครรับข้อมูล (คุณสามารถใช้AsObservable()กับหัวข้อของคุณเพื่อให้แน่ใจว่าไม่มีใครสามารถแคสต์ไปยังหัวเรื่องและทำให้สิ่งต่างๆยุ่งเหยิงได้)

คุณสามารถบรรลุสิ่งนี้ได้ด้วยเหตุการณ์. NET และการใช้งานFromEventPatternแต่ถ้าฉันแค่จะเปลี่ยนเหตุการณ์ให้เป็นIObservableอย่างไรก็ตามฉันไม่เห็นประโยชน์ของการมีเหตุการณ์แทนที่จะเป็นSubject(ซึ่งอาจหมายความว่าฉันหายไป บางอย่างที่นี่)

แต่สิ่งที่คุณควรหลีกเลี่ยงอย่างยิ่งค่อนข้างจะสมัครไปยังIObservableมีSubjectคือไม่ผ่านSubjectเข้าไปในIObservable.Subscribeวิธีการ


ทำไมคุณต้องมีรัฐเลย? ดังที่แสดงในคำตอบของฉันหากคุณแยกปัญหาออกเป็นส่วน ๆ คุณไม่จำเป็นต้องจัดการสถานะเลย ไม่ควรใช้หัวเรื่องในกรณีนี้
casperOne

8
@casperOne คุณไม่จำเป็นต้องมีสถานะภายนอก Subject <T> หรือเหตุการณ์ (ซึ่งทั้งสองมีคอลเล็กชันของสิ่งที่จะเรียกผู้สังเกตการณ์หรือตัวจัดการเหตุการณ์) ฉันแค่ต้องการใช้หัวเรื่องหากเหตุผลเดียวในการเพิ่มเหตุการณ์คือการห่อด้วย FromEventPattern นอกเหนือจากการเปลี่ยนแปลงในแผนผังข้อยกเว้นซึ่งอาจมีความสำคัญสำหรับคุณแล้วฉันไม่เห็นประโยชน์ที่จะหลีกเลี่ยงหัวเรื่องด้วยวิธีนี้ อีกครั้งฉันอาจจะพลาดอย่างอื่นที่นี่ซึ่งเหตุการณ์นั้นดีกว่าสำหรับเรื่อง การกล่าวถึงสถานะเป็นเพียงส่วนหนึ่งของคำพูดและดูเหมือนจะดีกว่าที่จะปล่อยไว้บางทีมันอาจจะชัดเจนกว่าหากไม่มีส่วนนั้น
Wilka

@casperOne - แต่คุณไม่ควรสร้างเหตุการณ์เพียงเพื่อรวมกับ FromEventPattern นั่นเป็นความคิดที่แย่มาก
James Moore

3
ฉันได้อธิบายคำพูดของฉันในเชิงลึกมากขึ้นในโพสต์บล็อกนี้
Dave Sexton

ฉันมักจะใช้เป็นจุดเข้าสู่ Rx นี่ตีตะปูบนหัวให้ฉัน ฉันมีสถานการณ์ที่มี API ที่เมื่อเรียกใช้จะสร้างเหตุการณ์ที่ฉันต้องการส่งผ่านไปป์ไลน์การประมวลผลปฏิกิริยา Subject คือคำตอบสำหรับฉันเนื่องจาก FromEventPattern ดูเหมือนจะไม่มีใน RxJava AFAICT
scorpiodawg

31

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

เมื่อคุณกำลังพยายามปรับการไหลของข้อมูลแบบอะซิงโครนัสเป็น Rx (หรือสร้างการไหลของข้อมูลแบบอะซิงโครนัสจากกระแสข้อมูลที่ไม่ใช่แบบอะซิงโครนัสในปัจจุบัน) กรณีที่พบบ่อยที่สุดคือ:

  • แหล่งที่มาของข้อมูลคือเหตุการณ์ : อย่างที่ลีพูดนี่เป็นกรณีที่ง่ายที่สุด: ใช้ FromEvent และมุ่งหน้าไปที่ผับ

  • แหล่งที่มาของข้อมูลจากการดำเนินงานซิงโครและคุณต้องการการปรับปรุงถึงขนาด (เช่นเว็บเซอร์หรือโทรฐานข้อมูล): Observable.Interval.Select(_ => <db fetch>)ในกรณีนี้คุณสามารถใช้วิธีการแนะนำของลีหรือสำหรับกรณีที่เรียบง่ายคุณสามารถใช้สิ่งที่ต้องการ คุณอาจต้องการใช้ DistinctUntilChanged () เพื่อป้องกันการเผยแพร่อัปเดตเมื่อไม่มีการเปลี่ยนแปลงใด ๆ ในแหล่งข้อมูล

  • แหล่งที่มาของข้อมูลคือ API แบบอะซิงโครนัสบางประเภทที่เรียกการโทรกลับของคุณ : ในกรณีนี้ให้ใช้ Observable.Create เพื่อเชื่อมต่อการโทรกลับของคุณเพื่อเรียก OnNext / OnError / OnComplete บนผู้สังเกตการณ์

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

การใช้ Observable.Create เทียบกับการสร้างคลาสที่จัดการหัวเรื่องนั้นค่อนข้างเทียบเท่ากับการใช้คีย์เวิร์ดผลตอบแทนเทียบกับการสร้างคลาสทั้งหมดที่ใช้ IEnumerator แน่นอนคุณสามารถเขียน IEnumerator ให้สะอาดและเป็นพลเมืองที่ดีได้เหมือนรหัสผลผลิต แต่อันไหนที่ห่อหุ้มได้ดีกว่าและให้ความรู้สึกที่ดีกว่า? เช่นเดียวกับ Observable สร้างเทียบกับการจัดการหัวเรื่อง

สังเกตได้การสร้างช่วยให้คุณมีรูปแบบที่ชัดเจนสำหรับการตั้งค่าที่ขี้เกียจและการล้างข้อมูล คุณจะบรรลุสิ่งนี้ได้อย่างไรด้วยการห่อหัวเรื่อง คุณต้องการวิธีการเริ่มต้น ... คุณจะรู้ได้อย่างไรว่าเมื่อใดควรเรียกมัน? หรือคุณเพิ่งเริ่มต้นใหม่เสมอทั้งๆที่ไม่มีใครฟัง? และเมื่อคุณทำเสร็จแล้วคุณจะหยุดอ่านจากซ็อกเก็ต / การสำรวจฐานข้อมูล ฯลฯ ได้อย่างไร คุณต้องมีเมธอด Stop บางประเภทและคุณยังต้องมีสิทธิ์เข้าถึงไม่เพียง แต่ IObservable ที่คุณสมัครเท่านั้น แต่ยังเป็นคลาสที่สร้าง Subject ตั้งแต่แรก

ด้วย Observable.Create ทั้งหมดนี้รวมอยู่ในที่เดียว เนื้อหาของ Observable.Create จะไม่ทำงานจนกว่าจะมีคนสมัครดังนั้นหากไม่มีใครสมัครคุณจะไม่ใช้ทรัพยากรของคุณ และ Observable.Create จะส่งคืน Disposable ที่สามารถปิดรีซอร์ส / การเรียกกลับของคุณได้อย่างหมดจด ฯลฯ ซึ่งเรียกว่าเมื่อ Observer ยกเลิกการสมัคร อายุการใช้งานของทรัพยากรที่คุณใช้ในการสร้าง Observable นั้นเชื่อมโยงกับอายุการใช้งานของ Observable


1
คำอธิบายที่ชัดเจนมากเกี่ยวกับ Observable.Create ขอบคุณ!
Evan Moran

1
ฉันยังมีกรณีที่ฉันใช้หัวเรื่องโดยที่วัตถุนายหน้าเปิดเผยสิ่งที่สังเกตได้ (พูดว่าเป็นเพียงคุณสมบัติที่เปลี่ยนแปลงได้) ส่วนประกอบต่างๆจะเรียกโบรกเกอร์เพื่อบอกเมื่อคุณสมบัตินั้นเปลี่ยนแปลง (ด้วยการเรียกใช้เมธอด) และเมธอดนั้นจะทำ OnNext ผู้บริโภคสมัครสมาชิก ฉันคิดว่าจะใช้ BehaviorSubject ในกรณีนี้เหมาะสมหรือไม่
Frank Schwieterman

1
มันขึ้นอยู่กับสถานการณ์ การออกแบบ Rx ที่ดีมีแนวโน้มที่จะเปลี่ยนระบบไปสู่สถาปัตยกรรม async / reactive อาจเป็นเรื่องยากที่จะรวมส่วนประกอบเล็ก ๆ ของรหัสปฏิกิริยาเข้ากับระบบที่จำเป็นต้องมีการออกแบบ โซลูชัน band aid คือการใช้ Subjects เพื่อเปลี่ยนการกระทำที่จำเป็น (การเรียกใช้ฟังก์ชันชุดคุณสมบัติ) ให้เป็นเหตุการณ์ที่สังเกตได้ จากนั้นคุณจะได้รับโค้ดปฏิกิริยาเพียงเล็กน้อยและไม่มี "aha!" ที่แท้จริง ช่วงเวลา. การเปลี่ยนการออกแบบเพื่อสร้างแบบจำลองการไหลของข้อมูลและตอบสนองต่อการออกแบบมักจะให้การออกแบบที่ดีขึ้น แต่เป็นการเปลี่ยนแปลงที่แพร่หลายและต้องมีการเปลี่ยนความคิดและการซื้อในทีม
Niall Connaughton

1
ฉันจะพูดตรงนี้ (ในฐานะ Rx ที่ไม่มีประสบการณ์) ว่า: โดยการใช้ Subjects คุณสามารถเข้าสู่โลกของ Rx ภายในแอปพลิเคชั่นที่จำเป็นที่โตขึ้นและค่อยๆเปลี่ยนมัน เพื่อรับประสบการณ์ครั้งแรก .... และหลังจากนั้นก็เปลี่ยนรหัสของคุณให้เป็นอย่างที่ควรจะเป็นตั้งแต่เริ่มต้น (ฮ่า ๆ ) แต่เริ่มต้นด้วยฉันคิดว่าการใช้วิชาจะคุ้มค่า
Robetto

9

ข้อความบล็อกที่ยกมาอธิบายได้ชัดเจนว่าทำไมคุณไม่ควรใช้Subject<T>แต่เพื่อให้ง่ายขึ้นคุณกำลังรวมฟังก์ชันของผู้สังเกตและผู้สังเกตเข้าด้วยกันในขณะที่ฉีดสถานะบางอย่างระหว่าง (ไม่ว่าคุณจะห่อหุ้มหรือขยาย)

นี่คือจุดที่คุณประสบปัญหา ความรับผิดชอบเหล่านี้ควรแยกจากกันและแตกต่างจากกัน

ที่กล่าวไว้โดยเฉพาะของคุณกรณีเราขอแนะนำให้คุณแบ่งข้อกังวลของคุณออกเป็นส่วนย่อย ๆ

ขั้นแรกคุณต้องมีเธรดของคุณที่ร้อนแรงและคอยตรวจสอบฮาร์ดแวร์เพื่อหาสัญญาณเพื่อแจ้งเตือน ปกติคุณจะทำอย่างไร เหตุการณ์ที่เกิดขึ้น เริ่มกันเลยดีกว่า

มากำหนดกันEventArgsว่างานของคุณจะเริ่มขึ้น

// The event args that has the information.
public class BaseFrameEventArgs : EventArgs
{
    public BaseFrameEventArgs(IBaseFrame baseFrame)
    {
        // Validate parameters.
        if (baseFrame == null) throw new ArgumentNullException("IBaseFrame");

        // Set values.
        BaseFrame = baseFrame;
    }

    // Poor man's immutability.
    public IBaseFrame BaseFrame { get; private set; }
}

ตอนนี้ชั้นที่จะจุดไฟในงาน. หมายเหตุนี้อาจจะมีระดับคงที่ (ตั้งแต่คุณมักจะมีหัวข้อการตรวจสอบการทำงานของบัฟเฟอร์ฮาร์ดแวร์) หรือบางสิ่งบางอย่างที่คุณเรียกตามความต้องการซึ่งเป็นสมาชิกที่ คุณจะต้องแก้ไขตามความเหมาะสม

public class BaseFrameMonitor
{
    // You want to make this access thread safe
    public event EventHandler<BaseFrameEventArgs> HardwareEvent;

    public BaseFrameMonitor()
    {
        // Create/subscribe to your thread that
        // drains hardware signals.
    }
}

ตอนนี้คุณมีชั้นเรียนที่เปิดเผยเหตุการณ์ Observables ทำงานได้ดีกับเหตุการณ์ต่างๆ มากจนมีการสนับสนุนชั้นหนึ่งสำหรับการแปลงสตรีมของเหตุการณ์ (คิดว่าสตรีมเหตุการณ์เป็นเหตุการณ์ที่เกิดขึ้นหลายครั้ง) ไปสู่IObservable<T>การใช้งานหากคุณทำตามรูปแบบเหตุการณ์มาตรฐานโดยใช้วิธีการแบบคงFromEventPatternที่ในObservableคลาสชั้นเรียน

ด้วยแหล่งที่มาของเหตุการณ์ของคุณและFromEventPatternวิธีการนี้เราสามารถสร้างIObservable<EventPattern<BaseFrameEventArgs>>ได้อย่างง่ายดาย ( EventPattern<TEventArgs>คลาสจะรวบรวมสิ่งที่คุณเห็นในเหตุการณ์. NET โดยเฉพาะอย่างยิ่งอินสแตนซ์ที่ได้มาจากEventArgsและอ็อบเจ็กต์ที่เป็นตัวแทนของผู้ส่ง) ดังนี้:

// The event source.
// Or you might not need this if your class is static and exposes
// the event as a static event.
var source = new BaseFrameMonitor();

// Create the observable.  It's going to be hot
// as the events are hot.
IObservable<EventPattern<BaseFrameEventArgs>> observable = Observable.
    FromEventPattern<BaseFrameEventArgs>(
        h => source.HardwareEvent += h,
        h => source.HardwareEvent -= h);

แน่นอนว่าคุณต้องการIObservable<IBaseFrame>แต่ทำได้ง่ายโดยใช้SelectวิธีการขยายในObservableคลาสเพื่อสร้างการฉายภาพ (เช่นเดียวกับที่คุณทำใน LINQ และเราสามารถสรุปทั้งหมดนี้ด้วยวิธีการที่ใช้งานง่าย):

public IObservable<IBaseFrame> CreateHardwareObservable()
{
    // The event source.
    // Or you might not need this if your class is static and exposes
    // the event as a static event.
    var source = new BaseFrameMonitor();

    // Create the observable.  It's going to be hot
    // as the events are hot.
    IObservable<EventPattern<BaseFrameEventArgs>> observable = Observable.
        FromEventPattern<BaseFrameEventArgs>(
            h => source.HardwareEvent += h,
            h => source.HardwareEvent -= h);

    // Return the observable, but projected.
    return observable.Select(i => i.EventArgs.BaseFrame);
}

7
ขอบคุณสำหรับการตอบกลับของคุณ @casperOne นี่เป็นแนวทางเริ่มต้นของฉัน แต่รู้สึก "ผิด" ที่จะเพิ่มเหตุการณ์เพื่อที่ฉันจะได้รวมกับ Rx ขณะนี้ฉันใช้ผู้รับมอบสิทธิ์ (และใช่ฉันรู้ว่านั่นคือเหตุการณ์ที่เกิดขึ้น!) เพื่อให้พอดีกับรหัสที่ใช้สำหรับการโหลดและบันทึกการกำหนดค่าสิ่งนี้จะต้องสามารถสร้างไปป์ไลน์ส่วนประกอบใหม่ได้และระบบผู้ร่วมประชุมให้ฉันมากที่สุด ความยืดหยุ่น Rx ทำให้ฉันปวดหัวในพื้นที่นี้ในตอนนี้ แต่พลังของทุกสิ่งทุกอย่างในเฟรมเวิร์กทำให้การแก้ปัญหา config คุ้มค่ามาก
Anthony

@ แอนโธนี่ถ้าคุณสามารถทำให้ตัวอย่างโค้ดของเขาใช้งานได้ดี แต่อย่างที่ฉันแสดงความคิดเห็นมันก็ไม่สมเหตุสมผล สำหรับความรู้สึก "ผิด" ฉันไม่รู้ว่าทำไมการแบ่งสิ่งต่างๆออกเป็นส่วนที่เป็นตรรกะจึงดูเหมือน "ผิด" แต่คุณไม่ได้ให้รายละเอียดเพียงพอในโพสต์ต้นฉบับของคุณเพื่อระบุวิธีที่ดีที่สุดในการแปลสิ่งนี้IObservable<T>เนื่องจากไม่มีข้อมูลว่าคุณเป็นอย่างไร กำลังส่งสัญญาณด้วยข้อมูลนั้น
casperOne

@casperOne ในความคิดของคุณการใช้ Subjects เหมาะสมกับ Message Bus / Event Aggregator หรือไม่?
kitsune

1
@kitsune ไม่ฉันไม่เห็นว่าทำไมพวกเขาถึง หากคุณกำลังคิดว่า "การเพิ่มประสิทธิภาพ" คุณต้องถามคำถามว่านั่นคือปัญหาหรือไม่คุณวัด Rx เป็นสาเหตุของปัญหาหรือไม่?
casperOne

2
ฉันเห็นด้วยกับ casperOne ที่นี่ว่าการแยกข้อกังวลเป็นความคิดที่ดี ฉันอยากจะชี้ให้เห็นว่าถ้าคุณใช้รูปแบบ Hardware to Event to Rx คุณจะสูญเสียความหมายของข้อผิดพลาด การเชื่อมต่อหรือเซสชันใด ๆ ที่สูญหาย ฯลฯ จะไม่เปิดเผยต่อผู้บริโภค ตอนนี้ผู้บริโภคไม่สามารถตัดสินใจได้ว่าต้องการลองใหม่ยกเลิกการเชื่อมต่อสมัครรับข้อมูลลำดับอื่นหรืออย่างอื่น
Lee Campbell

0

เป็นการไม่ดีที่จะสรุปว่า Subjects ไม่ดีที่จะใช้สำหรับอินเทอร์เฟซสาธารณะ แม้ว่าจะเป็นความจริงอย่างแน่นอน แต่นี่ไม่ใช่วิธีการเขียนโปรแกรมแบบปฏิกิริยา แต่เป็นตัวเลือกการปรับปรุง / การปรับโครงสร้างใหม่ที่ดีสำหรับโค้ดคลาสสิกของคุณ

หากคุณมีพร็อพเพอร์ตี้ปกติที่มีตัวเข้าถึงชุดสาธารณะและคุณต้องการแจ้งเกี่ยวกับการเปลี่ยนแปลงไม่มีอะไรจะพูดถึงการแทนที่ด้วย BehaviorSubject INPC หรือกิจกรรมอื่น ๆ เพิ่มเติมนั้นไม่สะอาดและเป็นการส่วนตัวทำให้ฉันผิดหวัง เพื่อจุดประสงค์นี้คุณสามารถและควรใช้ BehaviorSubjects เป็นคุณสมบัติสาธารณะแทนคุณสมบัติปกติและทิ้ง INPC หรือเหตุการณ์อื่น ๆ

นอกจากนี้อินเทอร์เฟซหัวเรื่องยังช่วยให้ผู้ใช้อินเทอร์เฟซของคุณทราบมากขึ้นเกี่ยวกับฟังก์ชันการทำงานของคุณสมบัติของคุณและมีแนวโน้มที่จะสมัครสมาชิกแทนที่จะได้รับค่า

วิธีที่ดีที่สุดในการใช้หากคุณต้องการให้ผู้อื่นรับฟัง / สมัครรับข้อมูลการเปลี่ยนแปลงของทรัพย์สิน

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.