我从历史数据库接收大约50 Hz的数据。我想在10 Hz左右将其传输到另一端。 为了达到这个目的,我产生了两个定时器,一个用于从历史数据库中获取数据(我的运行速度是发送定时器的两倍)。第二个定时器,为200ms(10 Hz)。 第一个计时器存储它在BlockingCollection中获得的每个值(我尝试过ConcurrentQueue)。由于两个不同的线程读/写集合,我不能使用普通的列表/队列。 这样做的挑战是它不会给我一个甚至50Hz。我希望每次通话都能获得5个左右的值,但它会以爆发形式提供数据(有时甚至没有,有时为15)。因此,我将BlockingCollection作为缓冲区,发送计时器从中获取数据。 发送计时器使用递归方法获取列表中间的数据。它检查值是否已发送,如果已经是(50Hz - > 10Hz),则获取下一个第5个值,如果不发送该值。 现在到了问题;递归方法有时会发送两次相同的值,即使我锁定了对象。我们已经确定问题是锁定没有按预期工作,但我们不知道为什么或如何解决它。
有什么建议吗? 附加的代码不是真正的代码,但它说明了问题,并且实际上为您提供了重复值,尽管不是很常见。
class Program {
private readonly ConcurrentDictionary<int, BlockingCollection<TestObject>> _pipes = new ConcurrentDictionary<int, BlockingCollection<TestObject>>();
private const int Interval = 5;
private Timer inTimer;
private Timer outTimer;
static void Main() {
Program program = new Program();
program.Run();
Console.ReadLine();
}
private void Run() {
_pipes[100] = new BlockingCollection<TestObject>();
_pipes[200] = new BlockingCollection<TestObject>();
_pipes[300] = new BlockingCollection<TestObject>();
inTimer = new Timer(InTimer, null, 0, 100);
Thread.Sleep(1000);
outTimer = new Timer(OutTimer, null, 0, 200);
}
private void OutTimer(object state) {
foreach (TestObject testObject in GetNotSentTestObjects()) {
Console.WriteLine("{0};{1};{2}", testObject.PipeId, testObject.Timestamp.ToString("o"), testObject.Value);
}
}
private IEnumerable<TestObject> GetNotSentTestObjects() {
List<TestObject> testObjects = new List<TestObject>();
foreach (KeyValuePair<int, BlockingCollection<TestObject>> pipe in _pipes) {
TestObject testObject = GetLatestTestObjectNotSent(pipe.Key, (pipe.Value.Count / 2));
if (testObject == null) {
return null;
}
testObjects.Add(testObject);
}
return testObjects;
}
private TestObject GetLatestTestObjectNotSent(int key, int locationInList) {
BlockingCollection<TestObject> pipe = _pipes[key];
TestObject testObject;
lock (pipe) {
testObject = pipe.ElementAt(locationInList - 1);
if (testObject.Sent) {
int nextLocationInList = locationInList + Interval;
GetLatestTestObjectNotSent(key, nextLocationInList);
}
testObject.Sent = true;
}
testObject.PipeId = key;
return testObject;
}
private void InTimer(object sender) {
Random random = new Random();
for (int i = 0; i < random.Next(0,20); i++) {
foreach (KeyValuePair<int, BlockingCollection<TestObject>> pipe in _pipes) {
pipe.Value.Add(new TestObject());
}
i++;
}
}
}
internal class TestObject {
public DateTime Timestamp { get; set; }
public string Value { get; set; }
public bool Sent { get; set; }
public int PipeId;
public TestObject() {
Value = Guid.NewGuid().ToString().Substring(0, 8);
Timestamp = DateTime.Now;
}
}
注意,Thread.Sleep是要填充列表。在我的代码中,我还有一些代码可以在一段时间后修剪列表。
答案 0 :(得分:1)
BlockingCollection
是一个队列。它不是为随机访问而设计的,并且试图随机访问它会导致你没有遇到麻烦。你也没有从队列中删除项目,这意味着你最终会耗尽内存。
您可能遇到的一个问题是,虽然您在使用ElementAt
访问列表时锁定了列表,但在添加项目时却没有锁定它。但是锁定并发数据结构的整个想法应该让你重新考虑你的设计。您不应该必须锁定并发数据结构。
据我了解,您想要选择尚未处理的每第5个项目。如果您不想丢弃未处理的项目(即项目1-4),那将成为一个相当困难的问题。如果您要丢弃这些项目,那么调用Take
五次就是一件简单的事情:
TestObject o;
for (int i = 0; i < 5; ++i)
{
o = pipe.Take();
}
// You now have the 5th item from the queue
// The other items have been discarded
如果您想保留这些项目以便稍后进行处理,则必须以某种方式将它们添加回队列。但它们会到达队列的末尾,这意味着您将在较新的项目之前处理旧项目。
此外,在某些时候你会填满队列,因为生产者加的东西比他们消费的速度快。
您需要更多地考虑您希望此应用程序如何工作,或者更好地向我们解释它。你所描述的内容以及你发布的代码都很混乱,使我们无法提出好的建议。
答案 1 :(得分:0)
相同的线程可以多次获得相同的锁。见这个例子
object o = new object();
lock (o) lock (o) Console.WriteLine("Acquired lock two times");
您应该使用其他同步原语,如Mutex,Semaphore,AutoResetEvent等...
答案 2 :(得分:0)
经过大量的抨击后,当我使用递归方法时,我需要返回...