2017-05-18 6 views
1

이벤트가 있습니다. 이벤트가 때때로 발생하고 Action<int> 인 이벤트 처리기를 호출합니다.이벤트의 값 수집

이제 이벤트가 전달하는 정수를 "수집"하여 목록에 저장하고 싶습니다. 또한 목록이 완성되고 새로운 목록이 시작되는 순간을 지정하려고합니다.

제가 생각하고있는 순진한 해결책은 속성이 List<int> ValList 인 것입니다. Event Handler는 호출 될 때마다 값을 추가하고 있습니다. 소비자 측에서 원할 때 목록을 가져오고 수시로 말합니다. ValList = new List<int>(); 스레드 동기화에 대한 문제를 피하려면 잠금이 필요합니다.

나는이 솔루션이 너무 심하게 추악하고 대안을 궁금해하고있었습니다. 시간이 지남에 따라 기능성 프로그래머가되어 점점 자주 사용하고 있습니다. 그러나 이런 문제에 관해서는 여전히 절차 적이라고 생각하고 있습니다. 정말 변경할 수있는 목록을 피하고 싶습니다 (여전히 System.Collections.Immutable을 사용하고 있습니다).

변경 가능 요소 및 부작용이없는 유용한 기능 솔루션이 있습니까?

+0

ConcurrentBag를 사용해보십시오. https://www.dotnetperls.com/concurrentbag – Trey

답변

0

Reactive Extensions를 사용하는 것이 좋습니다. 값의 흐름 (이벤트)을 처리하고 잠금 요구를 제거 할 수 있습니다.

먼저 Add, CompleteRequestView 작업에 대한 몇 가지 작업 클래스를 정의 할 예정입니다.

  • aggregator

    EventAction를 수집합니다 : 그것은 세 가지 수신 Subject 값을 개최한다

    public class EventAction 
    { 
        public static EventAction Add(int value) => new AddAction(value); 
        public static readonly RequestViewAction RequestView = new RequestViewAction(); 
        public static readonly EventAction Complete = new CompleteAction(); 
    } 
    
    public class AddAction : EventAction 
    { 
        public readonly int Value; 
        public AddAction(int value) => Value = value; 
    } 
    
    public class CompleteAction : EventAction 
    { 
    } 
    
    public class RequestViewAction : EventAction 
    { 
    } 
    

    다음으로는 AggregateView라는 형식을 만들려고 :이 예를 들어 F #에서 차별 노동 조합처럼 행동하는 것입니다 이벤트를 처리하고 집계 된 Lst<int> (Lst<int>the language-ext functional language extensions library에서 불변 목록 유형이지만 ImmutableList도 사용할 수 있음).

    using System; 
    using LanguageExt; 
    using static LanguageExt.Prelude; 
    using System.Reactive.Linq; 
    using System.Reactive.Subjects; 
    
    public class AggregateView : IDisposable 
    { 
        readonly Subject<EventAction> aggregator = new Subject<EventAction>(); 
        readonly Subject<int> events = new Subject<int>(); 
        readonly Subject<Lst<int>> view = new Subject<Lst<int>>(); 
    
        readonly IDisposable subscription; 
    
        public AggregateView() 
        { 
         // Creates an aggregate view of the integers that responds to various control 
         // actions coming through. 
         subscription = aggregator.Aggregate(
          Lst<int>.Empty, 
          (list, action) => 
          { 
           switch(action) 
           { 
            // Adds an item to the aggregate list and passes it on to the 
            // events Subject 
            case AddAction add: 
             events.OnNext(add.Value); 
             return list.Add(add.Value); 
    
            // Clears the list and passes a list onto the views Subject 
            case CompleteAction complete: 
             view.OnNext(Lst<int>.Empty); 
             return Lst<int>.Empty; 
    
            // Gets the current aggregate list and passes it onto the 
            // views Subject 
            case RequestViewAction req: 
             view.OnNext(list); 
             return list; 
    
            default: 
             return list; 
           } 
          }) 
          .Subscribe(x => { }); 
        } 
    
        /// <summary> 
        /// Observable stream of integer events 
        /// </summary> 
        public IObservable<int> Events => 
         events; 
    
        /// <summary> 
        /// Observable stream of list views 
        /// </summary> 
        public IObservable<Lst<int>> Views => 
         view; 
    
        /// <summary> 
        /// Listener for plugging into an event 
        /// </summary> 
        public void Listener(int value) => 
         aggregator.OnNext(EventAction.Add(value)); 
    
        /// <summary> 
        /// Clears the aggregate view and post it to Views 
        /// </summary> 
        public void Complete() => 
         aggregator.OnNext(EventAction.Complete); 
    
        /// <summary> 
        /// Requests a the current aggregate view to be pushed through to 
        /// the Views subscribers 
        /// </summary> 
        public void RequestView() => 
         aggregator.OnNext(EventAction.RequestView); 
    
        /// <summary> 
        /// Dispose 
        /// </summary> 
        public void Dispose() 
        { 
         subscription?.Dispose(); 
         view?.OnCompleted(); 
         events?.OnCompleted(); 
         view?.Dispose(); 
         events?.Dispose(); 
        } 
    } 
    

    그것은 두 IObservable 속성이 있습니다 : 단순히 Lst<int>보기 여기

의 스트림 될 것 정수 이벤트

  • views
  • 의 스트림이 될 것입니다
  • events
  • 는 클래스의

    • Views - 당신이

    는 또한 몇 가지 유용한 방법이 있습니다 정수 이벤트를 구독 할 수 있습니다 - 당신이 집계 목록

  • Events에 가입 할 수있는 :

    • Listener -이 무엇을 당신 event
    • Complete 이렇게하면 집계 목록이 비워져서 View 관측 가능
    • 으로 빈 목록이 전송됩니다.
    • RequestView - 이것은 현재 집계 목록을 관찰 가능한 Views의 모든 가입자에게 보냅니다.

  • 마지막으로 테스트하기 :

    class Program 
    { 
        static event Action<int> eventTest; 
    
        static void Main(string[] args) 
        { 
         var aggregate = new AggregateView(); 
         eventTest += aggregate.Listener; 
    
         aggregate.Views.Subscribe(ReceiveList); 
         aggregate.Events.Subscribe(ReceiveValue); 
    
         eventTest(1); 
         eventTest(2); 
         eventTest(3); 
         eventTest(4); 
         eventTest(5); 
    
         aggregate.RequestView(); 
         aggregate.Complete(); 
    
         eventTest(6); 
         eventTest(7); 
         eventTest(8); 
         eventTest(9); 
         eventTest(10); 
    
         aggregate.RequestView(); 
        } 
    
        static void ReceiveList(Lst<int> list) => 
         Console.WriteLine($"Got list of {list.Count} items: {ListShow(list)}"); 
    
        static void ReceiveValue(int x) => 
         Console.WriteLine(x); 
    
        static string ListShow(Lst<int> list) => 
         String.Join(", ", list); 
    } 
    

    이 내가 이벤트를 처리 할 때 생각할 수있는 가장 기능적인 방법입니다. Action<int>은 기본적으로 부작용이 있고 순수하지 않기 때문에 기능적으로 작업하려는 사람은 항상 붉은 색 플래그이어야합니다. 따라서 가능한 한 부작용을 캡슐화하고 다른 모든 것을 순수하게 만들어야합니다.

    그런데 모든 유형의 작업을 일반화 할 수 있습니다. 어떤 점에서 훨씬 유용합니다 :

    public enum EventActionTag 
    { 
        Add, 
        Complete, 
        RequestView 
    } 
    
    public class EventAction<T> 
    { 
        public readonly EventActionTag Tag; 
    
        public static EventAction<T> Add(T value) => new AddAction<T>(value); 
        public static readonly EventAction<T> RequestView = new RequestViewAction<T>(); 
        public static readonly EventAction<T> Complete = new CompleteAction<T>(); 
    
        public EventAction(EventActionTag tag) => 
         Tag = tag; 
    } 
    
    public class AddAction<T> : EventAction<T> 
    { 
        public readonly T Value; 
        public AddAction(T value) : base(EventActionTag.Add) => 
         Value = value; 
    } 
    public class CompleteAction<T> : EventAction<T> 
    { 
        public CompleteAction() : base(EventActionTag.Complete) 
        { } 
    } 
    public class RequestViewAction<T> : EventAction<T> 
    { 
        public RequestViewAction() : base(EventActionTag.RequestView) 
        { } 
    } 
    
    public class AggregateView<T> : IDisposable 
    { 
        readonly Subject<EventAction<T>> aggregator = new Subject<EventAction<T>>(); 
        readonly Subject<T> events = new Subject<T>(); 
        readonly Subject<Lst<T>> view = new Subject<Lst<T>>(); 
    
        readonly IDisposable subscription; 
    
        public AggregateView() 
        { 
         // Creates an aggregate view of the integers that responds to various control 
         // actions coming through. 
         subscription = aggregator.Aggregate(
          Lst<T>.Empty, 
          (list, action) => 
          { 
           switch(action.Tag) 
           { 
            // Adds an item to the aggregate list and passes it on to the 
            // events Subject 
            case EventActionTag.Add: 
             var add = (AddAction<T>)action; 
             events.OnNext(add.Value); 
             return list.Add(add.Value); 
    
            // Clears the list and passes a list onto the views Subject 
            case EventActionTag.Complete: 
             view.OnNext(Lst<T>.Empty); 
             return Lst<T>.Empty; 
    
            // Gets the current aggregate list and passes it onto the 
            // views Subject 
            case EventActionTag.RequestView: 
             view.OnNext(list); 
             return list; 
    
            default: 
             return list; 
           } 
          }) 
          .Subscribe(x => { }); 
        } 
    
        /// <summary> 
        /// Observable stream of integer events 
        /// </summary> 
        public IObservable<T> Events => 
         events; 
    
        /// <summary> 
        /// Observable stream of list views 
        /// </summary> 
        public IObservable<Lst<T>> Views => 
         view; 
    
        /// <summary> 
        /// Listener for plugging into an event 
        /// </summary> 
        public void Listener(T value) => 
         aggregator.OnNext(EventAction<T>.Add(value)); 
    
        /// <summary> 
        /// Clears the aggregate view and post it to Views 
        /// </summary> 
        public void Complete() => 
         aggregator.OnNext(EventAction<T>.Complete); 
    
        /// <summary> 
        /// Requests a the current aggregate view to be pushed through to 
        /// the Views subscribers 
        /// </summary> 
        public void RequestView() => 
         aggregator.OnNext(EventAction<T>.RequestView); 
    
        /// <summary> 
        /// Dispose 
        /// </summary> 
        public void Dispose() 
        { 
         subscription?.Dispose(); 
         view?.OnCompleted(); 
         events?.OnCompleted(); 
         view?.Dispose(); 
         events?.Dispose(); 
        } 
    } 
    
    +1

    와우, 놀라운! 이것은 내가 찾고 있었던 바로 그 것이다. 정말 고맙습니다! –

    +0

    문제 없으니 기꺼이 도와주세요. :) – louthster

    -1

    내게 무슨 말을하는지 완전히 이해하고 있다면 이벤트가 액션에 잠겨 있으며 해당 이벤트 서명을 제어 할 수 없습니다. 누적 된 정수로 목록을 검색하기 위해 외부 요청이 오는 시점까지 전달 된 각 int를 수집하려고합니다. 그러면 목록이 재설정되고 컬렉션에 대한 시간 정보가 저장됩니다. 그 맞습니까?

    나를 혼란스럽게하는 이유는 OOP로 간주되는 언어로 기능적 프로그래밍을 언급하는 이유입니다. LINQ가 어느 정도 기능적이라고 주장 할 수도 있지만, 확실히 기능적 사고를위한 더 나은 옵션이 있습니까? 왜냐하면 이것은 accumulator manager 클래스를 가진 꽤 쉬운 솔루션처럼 보이기 때문입니다.

    namespace bs 
    { 
    struct CollectionEvent 
    { 
        public DateTime Retrieved { get; set; }  
        public String IP { get; set; } 
    } 
    
    static class Accumulator 
    { 
        private static List<int> Items { get; set; } = new List<int>(); 
        private static bool Mutex { get; set; } = false; 
        private static List<CollectionEvent> Collections { get; set; } = new List<CollectionEvent>(); 
    
        public static void Add(int i) 
        { 
         Sync(() => Items.Add(i)); 
        } 
    
        public static List<int> Retrieve(String IP) 
        { 
         Collections.Add(new CollectionEvent 
         { 
          Retrieved = DateTime.UtcNow, 
          IP = IP 
         }); 
    
         List<int> dataOut = null; 
         Sync(() => 
         { 
          dataOut = new List<int>(Items); 
          Items = new List<int>(); 
         }); 
    
         return dataOut; 
        } 
    
        public static void Sync(Action fn) 
        { 
         const int Threshold = 10; 
         int attempts = 0; 
    
         for (; Mutex && (attempts < Threshold); attempts++) 
          Thread.Sleep(100 * attempts); 
    
         if (attempts == Threshold) 
          throw new Exception(); // or something better I'm sure 
    
         Mutex = true; 
         fn(); 
         Mutex = false; 
        } 
    } 
    
    class Program 
    { 
        static void Main(string[] args) 
        { 
         var r = new Random(); 
         var m = r.Next(5, 10); 
    
         for (int i = 0; i < m; i++) 
         { 
          var datum = r.Next(100, 10000); 
          Console.WriteLine($"COLLECT {datum}"); 
          Accumulator.Add(datum); 
         } 
    
         Console.WriteLine("RETRIEVE"); 
         Accumulator.Retrieve("0.0.0.0").ForEach(i => Console.WriteLine($"\t{i}")); 
    
         m = r.Next(5, 10); 
         for (int i = 0; i < m; i++) 
         { 
          var datum = r.Next(100, 10000); 
          Console.WriteLine($"COLLECT {datum}"); 
          Accumulator.Add(datum); 
         } 
    
         Console.WriteLine("RETRIEVE"); 
         Accumulator.Retrieve("0.0.0.0").ForEach(i => Console.WriteLine($"\t{i}")); 
    
         Console.Read(); 
        } 
    } 
    } 
    
    +0

    C#은 당신이 생각하든 말거나 상관없이 기능적 언어입니다. OO 세계에서 삶이 시작되었다고해서 그것이 지금 어디 있는지가 완전히 OO라는 것을 의미하지는 않습니다. 퍼스트 클래스 함수, lambdas, monadic expressions (LINQ); 사실 함수 프로그래밍에 필요한 모든 것. OP는 함수형 프로그래밍을 통해 분명히 빛을 보았지만 좋아하는 언어로 사용하려고합니다. 그래서 그가 잘못하고 있다고 말하면서 다른 언어를 사용해야하고, 그가 무엇을 요구하지 않았는지를 묻는 것은 내가 당신을 싫어하는 이유입니다. 나의 받아 들여진 응답을 아래로 투표하는 것은 유치하다. – louthster

    +0

    나는 당신을 투표하지 않았습니다. –