代码之家  ›  专栏  ›  技术社区  ›  George Johnston

在我的应用程序中线程安全的建议?

  •  1
  • George Johnston  · 技术社区  · 14 年前

    我的TcpServer中有以下方法

        private void Receive(ZClient client)
        {
            NetworkStream ns = client.TcpClient.GetStream();
            int bytesRead = 0;
            byte[] message = new byte[client.TcpClient.ReceiveBufferSize];
            while (true)
            {
                bytesRead = ns.Read(message, 0, client.TcpClient.ReceiveBufferSize);
                if (bytesRead == 0)
                {
                    break;  // Exit Thread
                }
                else
                {
                    Array.Resize<Byte>(ref client.Buffer, client.Buffer.Length + bytesRead);
                    Buffer.BlockCopy(message, 0, client.Buffer, client.Buffer.Length - bytesRead, bytesRead);
                    // Issue Below
                    DisassembleRawMessage(client); // *1 See below
                    new Thread(()=>DeQueueMessages(client)).Start();  // *2 See below
                    // End Issue
                }
            }
        }
    
    1. disassemblerawmessage负责处理我的缓冲区,并在我的客户机消息队列中设置单个消息。没问题。

    2. DeQueueMessages负责处理disAssemblerawMessage方法中先前放入我的客户端消息队列中的对象。

    这个问题似乎很明显。收到消息后,我就开始写一个新的帖子。在清除和处理单个消息的过程中,队列中可能添加了更多的项,因为interm中可能已接收到更多的数据。这会导致枚举错误,提示我在枚举期间不能修改集合的内容。

    我不想通过在同一个线程内处理队列来阻止recieve方法,因为它会破坏允许队列在接收数据的同时建立请求的目的。如何继续对对象排队,同时处理队列中的对象,而不出现枚举修改异常?

    更新:使消息出列的方法。

    while (client.Queue.Count() > 0)
    {
       byte[] buffer = client.Queue.Dequeue();
       ...
    }
    
    4 回复  |  直到 14 年前
        1
  •  2
  •   David Yaw    14 年前

    首先,我不会对收到的每条消息都使用新线程。要么启动等待新工作的线程,要么退出,或者使用线程池。

    如果在枚举过程中收到invalidooperationexception,则意味着您正在对列表或其他IEnumerable进行迭代。是不是说你在做这样的事?

    void DeQueueMessages(ZClient client)
    {
        foreach(Message m in client.messageList)
        {
            ...
        }
        messageList.Clear();
    }
    

    如果是,请切换到使用实际的队列类,并在while循环中执行此操作:

    void DeQueueMessages(ZClient client)
    {
        while(client.messageQueue.Count > 0)
        {
            Message m = client.messageQueue.Dequeue();
            ...
        }
    }
    

    如果内存可用,则标准的System.Collections.Generic.Queue类不是线程安全的,因此我将使用System.Collections.Concurrent.ConcurrentQueue。

        2
  •  1
  •   Steve Townsend    14 年前

    异步I/O通常是一个很好的解决方案,可以满足在网络I/O场景中对额外线程的需求。

    如果不想阻止读取,请通过 NetworkStream.BeginRead -不需要额外的线程,除非您希望回调将接收数据的长期工作移交给线程池执行。你可以用一个 concurrent collections 在.Net 4中,ConcurrentQueue或BlockingCollection。

    如果网络流上没有可用的数据,则此代码可能会导致紧循环。例子 here 建议你必须检查 DataAvailable 在这样的循环中调用Read之前。如果没有这个检查,这段代码看起来有问题。

        3
  •  0
  •   marr75    14 年前

    将消息复制到当前正在处理的第二个que,并清除传入的que。在复制和清除操作期间,您可能至少需要阻止。

    或者,在工作线程被隐藏时将单个消息处理分派给它们。

        4
  •  0
  •   Ondrej Tucny    14 年前

    您可以考虑使用 ThreadPool.QueueUserWorkItem 方法将要在线程池上处理的消息排队。

    优势:

    • 无障碍排队,
    • 由于更好的工作线程管理,降低了开销,
    • 用于整个应用程序的线程处理的单一机制。

    缺点:

    • 无法控制排队机制,
    • 无法控制线程分配策略。