BlockingCollection.TryTake()超过了超时

时间:2014-11-24 23:04:04

标签: c# multithreading collections thread-safety blockingcollection

在我的应用程序中,我有几个用于处理TCP连接的线程(一个用于读取,一个用于发送,一个用于处理新的连接)。每个线程处理所有客户端的给定操作类型,因此假设它将数据发送到不同IP上的5个TcpClient实例。我正在使用BlockingCollection作为缓冲区,因为我从发送线程访问它,但也从另一个生成要发送的数据的线程中访问它。我在发送线程中运行的函数如下所示:

    private void Sender()
    {
        while (run)
        {
            List<object[]> toRemove = new List<object[]>(); //bypass the fact that I cant remove a foreach iteration item from the BlockingCollection
            foreach (object[] obj in sendBuffer.ToList())
            {
                string IP = obj[0].ToString();
                string msg = obj[1].ToString();
                byte[] data = Encoding.UTF8.GetBytes(msg);
                foreach (TcpClient tcpc in TcpClients)
                {
                    if ((tcpc.Client.RemoteEndPoint as IPEndPoint).Address.ToString() == IP)
                    {
                        NetworkStream stream = tcpc.GetStream();
                        stream.Write(data, 0, data.Length);
                        break;
                    }
                }
                toRemove.Add(obj);
            }
            for (int i = 0; i < toRemove.Count; i++) //the bypass mentioned above
            {
                object[] rmv = toRemove[i];
                sendBuffer.TryTake(out rmv);
            }
        }
    }

注意:使用的BlockingCollection<object[]>类型。我的问题是,在某些流量点,缓冲区开始填满。我在缓冲区中设置了最多500条消息的限制,并且它很容易溢出。 现在,如果我理解正确(不确定),TryTake会做什么,它会尝试删除该项,如果此刻正在使用该集合,它会等待并再次尝试。 (注意:我也尝试将超时设置为50毫秒)。如果这是真的(如果没有,有人请纠正我并提出不同的理由),问题可能是集合在大多数时间调用TryTake时很忙。可能是那个吗?如果是的话,如何解决?

对于集合的使用,通过生成数据的线程,集合在foreach中每2秒访问一次,遍历1-80项的范围。缓冲区开始出现大约20多个问题,直到那时,它很好。 发送方线程现在只发送给一个客户端,之后它将最多发送到15个。因此在峰值中,这将是80个项目x 15个用户=每~2秒大约1200次访问。 非常感谢任何建议,谢谢。

1 个答案:

答案 0 :(得分:1)

TryTake没有像您所描述的那样运行,默认的BlockingCollection使用ConcurrentQueue来存储项目,而TryTake会将队列中的下一个项目分配给提供的out参考。

例如

BlockingCollection<object[]> sendBuffer = new BlockingCollection<object[]>();

object[] message = new object[2];
object[] message2 = new object[2];
// Add messages to the queue
sendBuffer.Add(message);
sendBuffer.Add(message2);

object[] toSend;
// Take next message off the queue
sendBuffer.TryTake(out toSend);

// toSend === message

在您的情况下,您可以使用BlockingCollection.Take()等待发送消息:

BlockingCollection<object[]> sendBuffer = new BlockingCollection<object[]>();
// CancellationTokenSource is used in place of your run variable.
System.Threading.CancellationTokenSource cancellationSource 
= new System.Threading.CancellationTokenSource();

    private void Sender()
    {
        // Exit loop if cancellation requested
        while (!cancellationSource.Token.IsCancellationRequested)
        {

            object[] obj;

            try {
                // Blocks until an item is available in sendBuffer or cancellationSource.Cancel() is called.
                obj = sendBuffer.Take(cancellationSource.Token);
            } catch (OperationCanceledException) {
                // OperationCanceledException will be thrown if cancellationSource.Cancel() 
                // is called during call to sendBuffer.Take
                break;
            } catch (InvalidOperationException) {
                // An InvalidOperationException means that Take() was called on a completed collection.
                // See BlockingCollection<T>.CompleteAdding
                break;
            }

            string IP = obj[0].ToString();
            string msg = obj[1].ToString();
            byte[] data = Encoding.UTF8.GetBytes(msg);
            foreach (TcpClient tcpc in TcpClients) {
                if ((tcpc.Client.RemoteEndPoint as IPEndPoint).Address.ToString() == IP) {
                    NetworkStream stream = tcpc.GetStream();
                    stream.Write(data, 0, data.Length);
                    break;
                }
            }               
        }
    }