我有一个System.Timers.Timer
,每隔 3秒
一旦它过去,我想把所有项目收集到我的集合中并一批处理。
这样做的动机是减少后端系统上的I / O数量。
挑战在于我有多个并发线程附加到集合/队列。因此我考虑使用ConcurrentQueue<T>
- 但这是一个糟糕的选择。
这个article on social msdn非常好地描述了这个问题。
我需要的是一个集合/队列,我可以一次获取所有数据(ToArray())并在一个原子操作中清除队列,这样我就不会丢失写入集合的任何数据/同时由其他线程排队。
private static void T1_Elapsed(object sender, ElapsedEventArgs e)
{
string[] result = _queue.ToArray();
_queue = new ConcurrentQueue<string>(); // strings will be lost :-)
}
我倾向于在简单的Queue<T>
上使用简单的基于锁的方法。
private static readonly object _myLock = new object();
private static void T1_Elapsed(object sender, ElapsedEventArgs e)
{
string[] result;
lock (_myLock)
{
result = _queue.ToArray();
_queue.Clear();
}
}
现在这段代码有一个明显的缺陷,可以在生产者代码中看到:
private static void ProduceItems()
{
//while (!_stop)
for(int i=0; i<int.MaxValue; i++)
{
if (_stop) break;
lock (_myLock) // bad. locks out other producers running on other threads.
{
Console.WriteLine("Enqueue " + i);
_queue.Enqueue("string" + i);
}
Thread.Sleep(1000); // FOR DEBUGGING PURPOSES ONLY
}
}
当然这段代码会锁定任何其他试图附加到队列的生产者。如果&#34; T1_Elapsed&#34;&#34; T1_Elapsed&#34;是否有任何方式我只能验证生产者的锁定。已设置锁?
有什么更适合我的问题吗?也许有什么可观察的?或者是否有任何好的&#34; batcher / aggregator&#34;实例
更新1:接收
很棒的你可以用RX做什么:)
我仍然在研究如何在这种情况下处理错误,重试或重新排队。
internal class Rx
{
internal static void Start()
{
ISubject<int> subject = new Subject<int>();
ISubject<int> syncedSubject = Subject.Synchronize(subject); // that should do it? - UNTESTED!
var subscription = syncedSubject.Buffer(TimeSpan.FromSeconds(5), 10)
.Subscribe((item) => ProcessBatch(item));
for (int i=1; i<int.MaxValue; i++)
{
syncedSubject.OnNext(i);
Thread.Sleep(200);
Console.WriteLine($"Produced {i}.");
}
Console.ReadKey();
subscription.Dispose();
}
private static void ProcessBatch(IList<int> list)
{
// Aggregate many into one
string joined = string.Join(" ", list);
// Process one
Console.WriteLine($"Wrote {joined} to remote storage.");
// how do you account for errors here?
myProducer.ReEnqueueMyFailedItems(list); // ?
}
}
答案 0 :(得分:1)
TPL DataFlow
我想说一下TPL DataFlow库吧。它建立在Task Paralled Library的基础上,专为满足并发性起着重要作用的这类需求而设计。有关此库的一系列博文,请参阅http://blog.stephencleary.com/2012/09/introduction-to-dataflow-part-1.html。
BatchBlock
似乎非常适合您的场景。有关教程,请参阅https://msdn.microsoft.com/en-us/library/hh228602(v=vs.110).aspx。
使用BatchBlock
的另一个例子:
https://taskmatics.com/blog/simplifying-producer-consumer-processing-with-tpl-dataflow-structures/
不是将数据发布到队列,而是发布到其中一个可用的TPL数据流块。
另一种选择可能是使用
反应性扩展
有关详细介绍,请参阅http://www.introtorx.com/uat/content/v1.0.10621.0/01_WhyRx.html
它还提供批处理支持:
void Sample()
{
var dataprovider = new Subject<int>();
var subscription = dataprovider
.Buffer(TimeSpan.FromMinutes(3))
.Subscribe(listOfNumbers =>
{
// do something with batch of items
var batchSize = listOfNumbers.Count;
});
for(int i = 0; i <= 5; ++i)
{
dataprovider.OnNext(i);
}
subscription.Dispose();
}
在上面的示例中,您需要进行一些修改才能使来自不同线程的多个生产者添加数据,请参阅reactive extension OnNext。它是简化代码(!),但它让您大致了解使用RX的概念。
可以使用最大缓冲区大小,给定时间段或两者的组合来完成缓冲。所以它也可以替换你的计时器。
您可以在OnNext
Subject
,而不是将项目添加到队列中
TPL DataFlow和RX都消除了使用需要清除的队列或类似的东西,因此它可以让你摆脱这种痛苦。