在做了一些研究后,我正在寻求有关如何有效删除Concurrent集合中的两个项目的任何反馈。我的情况涉及UDP上的传入消息,这些消息当前被放入BlockingCollection中。一旦集合中有两个用户,我需要安全地接管两个用户并处理它们。我已经看到了几种不同的技术,包括下面列出的一些想法。我目前的实现方式如下,但我认为有一种更简洁的方法可以确保用户以两人为一组进行处理。这是这种情况下的唯一限制。
当前实施:
private int userQueueCount = 0;
public BlockingCollection<User> UserQueue = new BlockingCollection<User>();
public void JoinQueue(User u)
{
UserQueue.Add(u);
Interlocked.Increment(ref userQueueCount);
if (userQueueCount > 1)
{
IEnumerable<User> users = UserQueue.Take(2);
if(users.Count==2) {
Interlocked.Decrement(ref userQueueCount);
Interlocked.Decrement(ref userQueueCount);
... do some work with users but if only one
is removed I'll run into problems
}
}
}
我想做的是这样的事情,但我目前无法在生产环境中对此进行测试以确保完整性。
Parallel.ForEach(UserQueue.Take(2), (u) => { ... });
或者更好:
public void JoinQueue(User u)
{
UserQueue.Add(u);
// if needed? increment
Interlocked.Increment(ref userQueueCount);
UserQueue.CompleteAdding();
}
然后在某处实现:
Task.Factory.StartNew(() =>
{
while (userQueueCount > 1) OR (UserQueue.Count > 1) If it's safe?
{
IEnumerable<User> users = UserQueue.Take(2);
... do stuff
}
});
这个问题是我不确定我能保证条件(Count&gt; 1)和Take(2)之间我确保UserQueue至少有两个要处理的项目?传入的UDP消息是并行处理的,所以我需要一种方法来安全地从阻塞/并发集合中将项目从两对中拉出来。
有更好/更安全的方法吗?
修订评论: 这个问题的目标实际上只是为了实现一个稳定/线程安全的方法来处理.Net 4.0中的并发集合中的项目。它不一定非常漂亮,它只需要在并行环境中处理无序二对中的项目时保持稳定。
答案 0 :(得分:2)
以下是我在粗略代码中所做的事情:
ConcurrentQueuequeue = new ConcurrentQueue(); //can use a BlockingCollection too (as it's just a blocking ConcurrentQueue by default anyway)
public void OnUserStartedGame(User joiningUser)
{
User waitingUser;
if (this.gameQueue.TryDequeue(out waitingUser)) //if there's someone waiting, we'll get him
this.MatchUsers(waitingUser, joiningUser);
else
this.QueueUser(joiningUser); //it doesn't matter if there's already someone in the queue by now because, well, we are using a queue and it will sort itself out.
}
private void QueueUser(User user)
{
this.gameQueue.Enqueue(user);
}
private void MatchUsers(User first, User second)
{
//not sure what you do here
}
基本的想法是,如果某人想要开始一个游戏并且队列中有人,则匹配他们并开始游戏 - 如果没有人,则将他们添加到队列中。 最好一次只有一个用户在队列中,但如果没有,那么,这也不是太糟糕,因为当其他用户启动游戏时,等待的用户将逐渐删除,并且在队列为空之前不会添加新的用户试。
答案 1 :(得分:1)
如果我出于某种原因无法将对用户放入集合中,我会使用ConcurrentQueue并尝试一次尝试TryDequeue 2项,如果我只能获得一项 - 将其放回去。等待。
答案 2 :(得分:1)
我认为这里最简单的解决方案是使用锁定:您将为所有消费者提供一个锁定(生产者不会使用任何锁定),这将确保您始终以正确的顺序接受用户:
User firstUser;
User secondUser;
lock (consumerLock)
{
firstUser = userQueue.Take();
secondUser = userQueue.Take();
}
Process(firstUser, secondUser);
另一个选择是拥有两个队列:一个用于单个用户,一个用于用户对,并且有一个进程将它们从第一个队列传输到第二个队列。
如果您不介意浪费其他线程,可以使用两个BlockingCollection
来执行此操作:
while (true)
{
var firstUser = incomingUsers.Take();
var secondUser = incomingUsers.Take();
userPairs.Add(Tuple.Create(firstUser, secondUser));
}
您不必担心此处的锁定,因为单个用户的队列只有一个消费者,而对的消费者现在可以安全地使用简单的Take()
。
如果你关心浪费线程并且可以使用TPL数据流,你可以使用BatchBlock<T>
,它将传入的项目组合成批量的 n 项目,其中 n 在创建块时配置,因此您可以将其设置为2.
答案 3 :(得分:0)
这可以帮助
public static IList<T> TakeMulti<T>(this BlockingCollection<T> me, int count = 100) where T : class
{
T last = null;
if (me.Count == 0)
{
last = me.Take(); // blocking when queue is empty
}
var result = new List<T>(count);
if (last != null)
{
result.Add(last);
}
//if you want to take more item on this time.
//if (me.Count < count / 2)
//{
// Thread.Sleep(1000);
//}
while (me.Count > 0 && result.Count <= count)
{
result.Add(me.Take());
}
return result;
}