我正在实现一个自动删除所有早于时间跨度的条目的集合。我决定使用Concurrent.BlockingCollection并作为测试我做了以下程序:
namespace CollectionTester {
class Program {
static void Main(string[] args) {
MyQueue queue = new MyQueue();
Sender thread = new Sender(queue);
Receiver rec = new Receiver(queue);
thread.Start();
System.Threading.Thread.Sleep(2000);
rec.Start();
Console.ReadKey();
}
}
public class MyQueue {
private System.Collections.Concurrent.BlockingCollection<DateTime> col = null;
private TimeSpan timeout = new TimeSpan(0, 0, 6);
private object _lock = new object();
public MyQueue() {
this.col = new System.Collections.Concurrent.BlockingCollection<DateTime>(5);
}
public System.Collections.Concurrent.BlockingCollection<DateTime> TheCollection {
get { return col; }
}
public void setVal(DateTime ts) {
Console.WriteLine("try to add " + ts.ToLongTimeString());
while (!col.TryAdd(ts)) {
deleteOlder();
}
ShowContent(" ++ ");
}
public void ShowContent(string tag) {
foreach (DateTime i in col) {
Console.WriteLine(tag + " " + i.ToLongTimeString());
}
}
private bool deleteOlder() {
DateTime old = DateTime.Now;
bool ret = false;
foreach (DateTime item in col) {
if (DateTime.Now.Subtract(item) >= timeout) {
col.TryTake(out old);
ret = true;
break;
}
}
if (ret == true) {
return deleteOlder();
}
else {
return ret;
}
}
}
public class Receiver {
private MyQueue theCollection = null;
public Receiver(MyQueue thecol) {
this.theCollection = thecol;
}
public void Start() {
Task.Factory.StartNew(() => {
while (theCollection.TheCollection.Count > 0) {
try {
DateTime ts = DateTime.Now;
if (theCollection.TheCollection.TryTake(out ts)) {
Console.WriteLine(" --> " + ts.ToLongTimeString());
theCollection.ShowContent(" -- " );
System.Threading.Thread.Sleep(2000);
}
} catch {
Console.WriteLine(" --> Error fectching Item");
}
}
Console.WriteLine("Nothing in the queue");
});
}
}
public class Sender {
private MyQueue theCollection = null;
public Sender(MyQueue thecol) {
this.theCollection = thecol;
}
public void Start() {
Task.Factory.StartNew(() => {
for (int i = 1; i <= 10; i++) {
Console.WriteLine("Iteration: " + i.ToString());
DateTime ts = DateTime.Now;
theCollection.setVal(ts);
System.Threading.Thread.Sleep(1000);
}
Console.WriteLine("Nothing else to add...");
});
}
}
}
这完全正常,但如果我使用GetConsumingEnumerable枚举器,程序将被阻止。
尝试用以下简单的方法代替:
public void ShowContent(string tag) {
foreach (DateTime i in col.GetConsumingEnumerable()) {
Console.WriteLine(tag + " " + i.ToLongTimeString());
}
col.CompleteAdding();
}
并且程序将被破坏。
对我来说,这没有任何意义,GetConsumingEnumerable应该是标准枚举器的一个更安全的选项,因为它使用集合的副本,但它阻止了程序。我尝试了锁,没有任何改变。
我是否以错误的方式理解这个集合,或者这可能是一个错误?或者你看到我的实施存在缺陷?谢谢!
答案 0 :(得分:1)
是的,有一个漏洞没问题。 BlockingCollection
旨在由至少两个线程使用:一个添加项目,另一个消费。我想你已经意识到了,但是你所做的就是创建一个线程来完成所有工作,而你的主线程什么都不做。 Sender
调用setVal
来添加项目,然后立即从集合中读取它们。这总是会给出奇怪的结果。
相反,您需要Sender
类在单独的线程上添加项目。您的主要帖子应致电Receiver
以使用GetConsumingEnumerable
阅读相关项目。您只需要查看BlockingCollection
的一些示例,以了解它应该如何使用。
答案 1 :(得分:1)
我认为你在迭代时会出现问题。注意在下面的GetConsumingEnumerable示例中,如何在循环之前完成添加,以防止循环挂起... Taken from this MSDN entry
class ConsumingEnumerableDemo
{
// Demonstrates:
// BlockingCollection<T>.Add()
// BlockingCollection<T>.CompleteAdding()
// BlockingCollection<T>.GetConsumingEnumerable()
public static void BC_GetConsumingEnumerable()
{
using (BlockingCollection<int> bc = new BlockingCollection<int>())
{
// Kick off a producer task
Task.Factory.StartNew(() =>
{
for (int i = 0; i < 10; i++)
{
bc.Add(i);
Thread.Sleep(100); // sleep 100 ms between adds
}
// Need to do this to keep foreach below from hanging
bc.CompleteAdding();
});
// Now consume the blocking collection with foreach.
// Use bc.GetConsumingEnumerable() instead of just bc because the
// former will block waiting for completion and the latter will
// simply take a snapshot of the current state of the underlying collection.
foreach (var item in bc.GetConsumingEnumerable())
{
Console.WriteLine(item);
}
}
}
}
答案 2 :(得分:0)
我找到了关于这个课程的好博客文章,现在我真的理解这个方法的用法:
http://www.codethinked.com/blockingcollection-and-iproducerconsumercollection
关键是这个可枚举确实是某种阻塞点,等待新项目,并且只应在消费者中使用,而不是普通的枚举器。
我的例子的代码现在是:
namespace CollectionTester {
class Program {
static void Main(string[] args) {
MyQueue queue = new MyQueue();
Sender thread = new Sender(queue);
Receiver rec = new Receiver(queue);
thread.Start();
System.Threading.Thread.Sleep(2000);
rec.Start();
Console.ReadKey();
}
}
public class MyQueue {
private System.Collections.Concurrent.BlockingCollection<DateTime> col = null;
private TimeSpan timeout = new TimeSpan(0, 0, 6);
public MyQueue() {
this.col = new System.Collections.Concurrent.BlockingCollection<DateTime>(5);
}
public System.Collections.Concurrent.BlockingCollection<DateTime> TheCollection {
get { return col; }
}
public void setVal(DateTime ts) {
Console.WriteLine("try to add " + ts.ToLongTimeString());
while (!col.TryAdd(ts)) {
deleteOlder();
}
ShowContent(" ++ ");
}
public void ShowContent(string tag) {
foreach (DateTime i in col) {
Console.WriteLine(tag + " " + i.ToLongTimeString());
}
}
private bool deleteOlder() {
DateTime old = DateTime.Now;
bool ret = false;
foreach (DateTime item in col) {
if (DateTime.Now.Subtract(item) >= timeout) {
col.TryTake(out old);
ret = true;
break;
}
}
if (ret == true) {
return deleteOlder();
}
else {
return ret;
}
}
}
public class Receiver {
private MyQueue theCollection = null;
public Receiver(MyQueue thecol) {
this.theCollection = thecol;
}
public void Start() {
Task.Factory.StartNew(() => {
foreach (DateTime value in theCollection.TheCollection.GetConsumingEnumerable()) {
Console.WriteLine(" >>> " + value.ToLongTimeString());
theCollection.ShowContent(" -- ");
System.Threading.Thread.Sleep(2000);
}
Console.WriteLine("Nothing in the queue");
});
}
}
public class Sender {
private MyQueue theCollection = null;
public Sender(MyQueue thecol) {
this.theCollection = thecol;
}
public void Start() {
Task.Factory.StartNew(() => {
for (int i = 1; i <= 10; i++) {
Console.WriteLine("Iteration: " + i.ToString());
DateTime ts = DateTime.Now;
theCollection.setVal(ts);
System.Threading.Thread.Sleep(1000);
}
Console.WriteLine("Nothing else to add...");
theCollection.TheCollection.CompleteAdding();
});
}
}
}