[c#] .NET Reactive Extensions에서 주제가 권장되지 않는 이유는 무엇입니까?

저는 현재 .NET 용 Reactive Extensions 프레임 워크를 이해하고 있으며 제가 찾은 다양한 소개 리소스 (주로 http://www.introtorx.com )를 통해 작업하고 있습니다.

우리의 응용 프로그램에는 네트워크 프레임을 감지하는 여러 하드웨어 인터페이스가 포함됩니다. 이러한 인터페이스는 내 IObservable이됩니다. 그런 다음 이러한 프레임을 사용하거나 데이터에 대해 어떤 방식으로 변환을 수행하고 새로운 유형의 프레임을 생성하는 다양한 구성 요소를 갖게됩니다. 예를 들어 매 n 번째 프레임을 표시해야하는 다른 구성 요소도 있습니다. Rx가 우리 애플리케이션에 유용 할 것이라고 확신하지만 IObserver 인터페이스에 대한 구현 세부 사항에 어려움을 겪고 있습니다.

내가 읽은 대부분의 (모두는 아니지만) 리소스는 IObservable 인터페이스를 직접 구현하지 말고 제공된 함수 또는 클래스 중 하나를 사용해야한다고 말했습니다. 내 연구에 따르면를 생성하면 필요한 Subject<IBaseFrame>것을 제공 할 수 있는 것으로 나타났습니다 . 하드웨어 인터페이스에서 데이터를 읽은 다음 Subject<IBaseFrame>인스턴스 의 OnNext 함수를 호출하는 단일 스레드를 갖게 됩니다. 그러면 다른 IObserver 구성 요소가 해당 주제로부터 알림을받습니다.

내 혼란은 이 튜토리얼 의 부록에있는 조언에서 비롯됩니다 .

주제 유형을 사용하지 마십시오. Rx는 효과적으로 함수형 프로그래밍 패러다임입니다. 주체를 사용한다는 것은 잠재적으로 변경 될 수있는 상태를 관리하고 있음을 의미합니다. 변경 상태와 비동기 프로그래밍을 동시에 처리하는 것은 제대로하기가 매우 어렵습니다. 또한 많은 연산자 (확장 메서드)는 구독 및 시퀀스의 정확하고 일관된 수명이 유지되도록주의 깊게 작성되었습니다. 주제를 소개 할 때 이것을 깨뜨릴 수 있습니다. 주제를 명시 적으로 사용하는 경우 향후 릴리스에서도 상당한 성능 저하가 발생할 수 있습니다.

내 애플리케이션은 성능이 매우 중요합니다. 프로덕션 코드에 들어가기 전에 Rx 패턴을 사용하여 성능을 테스트 할 것입니다. 그러나 저는 Subject 클래스를 사용하여 Rx ​​프레임 워크의 정신에 위배되는 작업을하고 있으며 프레임 워크의 향후 버전이 성능을 저하시킬 것이라고 걱정합니다.

내가 원하는 일을하는 더 좋은 방법이 있습니까? 관찰자가 있든 없든 하드웨어 폴링 스레드는 지속적으로 실행될 것이므로 (그렇지 않으면 HW 버퍼가 백업 할 것임) 이것은 매우 핫한 시퀀스입니다. 그런 다음 수신 된 프레임을 여러 관찰자에게 전달해야합니다.

어떤 조언이라도 대단히 감사하겠습니다.



답변

좋아, 우리가 내 독단적 인 방식을 무시하고 “주체가 좋다 / 나쁘다”를 모두 무시한다면. 문제 공간을 살펴 보겠습니다.

나는 당신이 이해해야 할 시스템의 두 가지 스타일 중 하나를 가지고 있다고 확신합니다.

  1. 메시지가 도착하면 시스템이 이벤트 또는 콜백을 발생시킵니다.
  2. 처리 할 메시지가 있는지 확인하려면 시스템을 폴링해야합니다.

옵션 1의 경우 간단합니다. 적절한 FromEvent 메서드로 래핑하면됩니다. 술집에!

옵션 2의 경우 이제이를 폴링하는 방법과이를 효율적으로 수행하는 방법을 고려해야합니다. 또한 가치를 얻었을 때 어떻게 게시합니까?

폴링 전용 스레드가 필요하다고 생각합니다. 다른 코더가 ThreadPool / TaskPool을 망치고 ThreadPool 고갈 상황에 빠지는 것을 원하지 않을 것입니다. 또는 컨텍스트 전환의 번거 로움을 원하지 않습니다. 따라서 자체 스레드가 있다고 가정하면 폴링을 위해 앉아있는 While / Sleep 루프가있을 것입니다. 수표에서 일부 메시지를 찾으면이를 게시합니다. 이 모든 것이 Observable.Create에 완벽하게 들립니다. 이제 취소를 허용하기 위해 Disposable을 반환 할 수 없기 때문에 While 루프를 사용할 수 없습니다. 다행히도 전체 책을 읽었으므로 재귀 스케줄링에 능숙합니다!

나는 이와 같은 것이 효과가있을 것이라고 상상한다. #검증되지 않은

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.
    }
}

제가 주제를 정말 좋아하지 않는 이유는 대개 개발자가 문제에 대한 명확한 디자인을 가지고 있지 않은 경우입니다. 주제를 해킹하고 여기저기서 찔러 넣은 다음 WTF에서 지원 개발자가 추측 한 내용이 계속 진행되도록합니다. Create / Generate etc 메서드를 사용하면 시퀀스에 대한 효과를 지역화하는 것입니다. 하나의 방법으로 모든 것을 볼 수 있으며 다른 누구도 불쾌한 부작용을 일으키지 않는다는 것을 알 수 있습니다. 주제 필드가 보이면 이제 사용중인 클래스의 모든 장소를 찾아야합니다. 일부 MFer가 공개적으로 공개하면 모든 베팅이 해제됩니다. 누가이 시퀀스가 ​​사용되는지 알고 있습니다! Async / Concurrency / Rx는 어렵습니다. 부작용과 인과 관계 프로그래밍이 머리를 더 많이 돌리도록 허용하여 더 어렵게 만들 필요가 없습니다.


답변

일반적으로를 사용하지 않는 Subject것이 좋지만 여기서하는 일에 대해서는 꽤 잘 작동한다고 생각합니다. 내가 물어 비슷한 질문을 내가 수신 튜토리얼에서 “회피 대상”메시지를 가로 질러 왔을 때.

Dave Sexton (Rxx의) 을 인용하려면

“대상은 Rx의 상태 저장 구성 요소입니다. 필드 또는 지역 변수로 이벤트와 같은 관찰 가능 항목을 만들어야 할 때 유용합니다.”

나는 그것들을 Rx의 진입 점으로 사용하는 경향이 있습니다. 내가 요구 (당신이 가지고있는 것처럼) ‘뭔가 일이’말을 몇 가지 코드가 그렇다면, 내가 사용하는 것이 Subject및 전화를 OnNext. 그런 다음 IObservable다른 사람들이 구독 할 수 있도록 노출합니다 ( AsObservable()주제에 사용 하여 아무도 주제에 캐스팅하여 일을 엉망으로 만들 수 없도록 할 수 있습니다).

또한 .NET 이벤트 및 사용에이를 수있는 FromEventPattern,하지만 난 단지에 이벤트를 설정하는거야 경우 IObservable어쨌든, 나는 대신의 이벤트가있는의 혜택을 볼 수 없습니다 Subject내가 부족 의미 할 수 있습니다 ( 여기에 뭔가)

그러나, 당신이 아주 강하게 피해야 할 것은 IObservablewith a를 구독하는 것 입니다. SubjectSubject, IObservable.Subscribe메소드에 a 를 전달하지 마십시오 .


답변

종종 주제를 관리 할 때 실제로 이미 Rx에있는 기능을 다시 구현하고 있으며 강력하고 간단하며 확장 할 수있는 방식은 아닙니다.

일부 비동기 데이터 흐름을 Rx로 조정 (또는 현재 비동기가 아닌 데이터 흐름에서 비동기 데이터 흐름 생성)하려는 경우 가장 일반적인 경우는 다음과 같습니다.

  • 데이터 소스는 이벤트입니다 . Lee가 말했듯이 이것이 가장 간단한 경우입니다. FromEvent를 사용하고 펍으로 향합니다.

  • 데이터 소스는 동기식 작업에서 가져오고 폴링 된 업데이트를 원합니다 (예 : 웹 서비스 또는 데이터베이스 호출) :이 경우 Lee의 제안 된 접근 방식을 사용할 수 있으며 간단한 경우에는 Observable.Interval.Select(_ => <db fetch>). DistinctUntilChanged ()를 사용하여 소스 데이터가 변경되지 않은 경우 업데이트 게시를 방지 할 수 있습니다.

  • 데이터 소스는 콜백을 호출하는 일종의 비동기 API입니다 .이 경우 Observable.Create를 사용하여 콜백을 연결하여 관찰자에서 OnNext / OnError / OnComplete를 호출합니다.

  • 데이터 소스는 새 데이터를 사용할 수있을 때까지 차단하는 호출입니다 (예 : 일부 동기 소켓 읽기 작업) :이 경우 Observable.Create를 사용하여 소켓에서 읽고 Observer에 게시하는 명령형 코드를 래핑 할 수 있습니다. 데이터를 읽을 때. 이것은 당신이 주제로하는 일과 유사 할 수 있습니다.

Observable.Create를 사용하는 것과 Subject를 관리하는 클래스를 만드는 것은 yield 키워드를 사용하는 것과 IEnumerator를 구현하는 전체 클래스를 만드는 것과 상당히 동일합니다. 물론 IEnumerator는 yield 코드만큼 깔끔하고 좋은 시민이되도록 작성할 수 있지만 어느 것이 더 잘 캡슐화되고 깔끔한 디자인을 느끼는가? Observable.Create vs. Subject도 마찬가지입니다.

Observable.Create는 게으른 설정과 깨끗한 분해를위한 깨끗한 패턴을 제공합니다. 주제를 래핑하는 클래스로 어떻게 이것을 달성합니까? 어떤 종류의 Start 메서드가 필요합니다. 언제 호출해야하는지 어떻게 알 수 있습니까? 아니면 아무도 듣지 않을 때도 항상 시작합니까? 완료되면 소켓에서 읽기 / 데이터베이스 폴링 등을 중지하려면 어떻게해야합니까? 어떤 종류의 Stop 메서드가 있어야하며 구독중인 IObservable뿐만 아니라 처음에 Subject를 만든 클래스에 대한 액세스 권한이 있어야합니다.

Observable.Create를 사용하면 모든 것이 한곳에 정리됩니다. Observable.Create의 본문은 누군가 구독 할 때까지 실행되지 않으므로 아무도 구독하지 않으면 리소스를 사용하지 않습니다. 그리고 Observable.Create는 리소스 / 콜백 등을 완전히 종료 할 수있는 Disposable을 반환합니다. 이것은 Observer가 구독을 취소 할 때 호출됩니다. Observable을 생성하는 데 사용하는 리소스의 수명은 Observable 자체의 수명과 깔끔하게 연결됩니다.


답변

인용 된 블록 텍스트는 왜를 사용하지 말아야하는지 설명 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.
    }
}

이제 이벤트를 노출하는 클래스가 있습니다. Observable은 이벤트와 잘 작동합니다. 클래스 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);
}


답변

주제가 공용 인터페이스에 사용하기에 좋지 않다는 것을 일반화하는 것은 좋지 않습니다. 확실히 사실이지만, 이것이 리 액티브 프로그래밍 접근 방식이 아니라는 것은 확실하지만 클래식 코드를위한 좋은 개선 / 리팩토링 옵션입니다.

public set 접근자가있는 일반 속성이 있고 변경 사항에 대해 알리려는 경우 BehaviorSubject로 대체하는 것에 대해 아무 말도하지 않습니다. INPC 또는 기타 다른 이벤트는 그다지 깨끗하지 않으며 개인적으로 나를 지치게합니다. 이를 위해 일반 속성 대신 공용 속성으로 BehaviorSubjects를 사용하고 INPC 또는 기타 이벤트를 버릴 수 있습니다.

또한 주제 인터페이스는 인터페이스의 사용자가 속성의 기능에 대해 더 잘 알 수있게하고 단순히 가치를 얻는 대신 구독 할 가능성이 더 높습니다.

다른 사람이 속성의 변경 사항을 듣거나 구독하도록하려면 사용하는 것이 가장 좋습니다.


답변