我有一个活动。
该事件不时被触发,它调用的事件处理程序为Action<int>
。
现在我想&#34;收集&#34;事件传递给我的这些整数并将它们保存在列表中。我还想指定列表完成并启动新列表的时刻。
我想到的天真解决方案是属性List<int> ValList
。
事件处理程序每次调用时都会添加一个值。
消费者一方在需要的时候拿着名单,并且会不时地说ValList = new List<int>();
为了避免线程同步问题,我也需要锁定。
我发现这个解决方案非常丑陋,并且想知道其他选择。 随着时间的推移,我越来越成为一名功能性程序员,而且我经常使用它。 但是当涉及到这样的问题时,我仍然在考虑程序问题。 我真的想避免使用可变列表(我仍在使用System.Collections.Immutable)。
是否有一个没有可变性和副作用的好功能解决方案?
答案 0 :(得分:0)
您应该考虑使用Reactive Extensions。它处理值(事件)流,并且可以消除锁的需要。
首先,我要为Add
,Complete
和RequestView
操作定义一些操作类。这在F#中表现得像一个有区别的联合,例如:
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
的类型,它将包含三个Rx Subject
值:
aggregator
将收集EventAction
个事件并管理汇总的Lst<int>
(Lst<int>
是来自the language-ext functional language extensions library的不可变列表类型,但您可以使用{ {1}}也是。)ImmutableList
,它只是整数事件的流events
这将是views
次观看这是班级:
Lst<int>
它有两个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
- 允许您订阅汇总列表Views
- 允许您订阅整数事件还有一些有用的方法:
Events
- 这就是您插入Listener
event
- 这将清空聚合列表并将空列表发送到Complete
observable 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>
答案 1 :(得分:-1)
如果我完全理解您所说的事件已被锁定为操作,并且您无法控制该事件签名。您希望收集传递的每个int,直到某个外部请求来检索具有任何累积整数的列表,此时列表将重置并保存有关该集合的时间信息。这是对的吗?
让我感到困惑的是你为什么用一种意味着OOP的语言提到函数式编程?你可能会认为LINQ在某种程度上是功能性的,但是对于具有功能意识的人来说肯定有更好的选择吗?因为这对于累加器管理器类来说似乎是一个非常简单的解决方案。
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();
}
}
}