System.Collections.Concurrent.BlockingCollection未按预期工作

时间:2014-02-24 12:03:56

标签: c# multithreading collections

我正在实现一个自动删除所有早于时间跨度的条目的集合。我决定使用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应该是标准枚举器的一个更安全的选项,因为它使用集合的副本,但它阻止了程序。我尝试了锁,没有任何改变。

我是否以错误的方式理解这个集合,或者这可能是一个错误?或者你看到我的实施存在缺陷?谢谢!

3 个答案:

答案 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();
            });
        }
    }
}