放得下 發表於 2025-12-19 09:37:58

C# BlockingCollection的使用小结

<div id="navCategory"><h5 class="catalogue">目录</h5><ul class="first_class_ul"><li>什么是BlockingCollection&lt;T&gt;</li><li>主要特点</li><li>构造函数</li><li>常用方法</li><ul class="second_class_ul"><li>生产者操作</li><li>消费者操作</li></ul><li>示例代码</li><ul class="second_class_ul"></ul><li>注意事项</li><ul class="second_class_ul"></ul><li>串口接收</li><ul class="second_class_ul"><li>底层存储的类型</li><li>线程安全和并发访问</li><li>串口数据接收的顺序性</li><li>关键点</li></ul></ul></div><p class="maodian"></p><h2>什么是BlockingCollection&lt;T&gt;</h2>
<p><code>BlockingCollection&lt;T&gt;</code> 是一个线程安全的集合,它提供了一种机制,允许一个或多个生产者线程将数据添加到集合中,同时允许一个或多个消费者线程从集合中取出数据。它内部封装了一个线程安全的集合(如 <code>ConcurrentQueue&lt;T&gt;</code>、<code>ConcurrentStack&lt;T&gt;</code> 或 <code>ConcurrentBag&lt;T&gt;</code>),并提供了阻塞和限制集合大小的功能。</p>
<p class="maodian"></p><h2>主要特点</h2>
<ul><li>线程安全:内部使用锁或其他同步机制,确保在多线程环境下对集合的操作是安全的。</li><li>阻塞操作:当集合为空时,消费者线程会阻塞等待,直到有数据可用;当集合达到最大容量时,生产者线程会阻塞等待,直到有空间可用。</li><li>限制大小:可以通过构造函数指定集合的最大容量。</li><li>支持多种底层集合:可以使用 <code>ConcurrentQueue&lt;T&gt;</code>(默认)、<code>ConcurrentStack&lt;T&gt;</code> 或 <code>ConcurrentBag&lt;T&gt;</code> 作为底层存储结构。</li></ul>
<p class="maodian"></p><h2>构造函数</h2>
<p><code>BlockingCollection&lt;T&gt;</code> 提供了多种构造方式:</p>
<div class="jb51code"><pre class="brush:csharp;">// 使用默认的 ConcurrentQueue&lt;T&gt;,无容量限制
var blockingCollection = new BlockingCollection&lt;int&gt;();

// 使用默认的 ConcurrentQueue&lt;T&gt;,并指定最大容量
var blockingCollection = new BlockingCollection&lt;int&gt;(10);

// 指定底层集合类型
var blockingCollection = new BlockingCollection&lt;int&gt;(new ConcurrentStack&lt;int&gt;());
</pre></div>
<p class="maodian"></p><h2>常用方法</h2>
<p class="maodian"></p><h3>生产者操作</h3>
<ul><li><code>Add(T item)</code>:将一个元素添加到集合中。如果集合已满,会抛出异常。</li><li><code>TryAdd(T item)</code>:尝试将一个元素添加到集合中。如果集合已满,返回 <code>false</code>。</li><li><code>TryAdd(T item, TimeSpan timeout)</code>:尝试在指定的超时时间内将元素添加到集合中。</li><li><code>CompleteAdding()</code>:标记集合不再添加新的元素。消费者线程在集合为空时会收到通知并退出。</li></ul>
<p class="maodian"></p><h3>消费者操作</h3>
<ul><li><code>Take()</code>:从集合中取出一个元素。如果集合为空,线程会阻塞等待。</li><li><code>TryTake(out T item)</code>:尝试从集合中取出一个元素。如果集合为空,返回 <code>false</code>。</li><li><code>TryTake(out T item, TimeSpan timeout)</code>:尝试在指定的超时时间内从集合中取出一个元素。</li><li><code>GetConsumingEnumerable()</code>:返回一个可枚举的集合,消费者可以使用 <code>foreach</code> 遍历集合中的元素。当调用 <code>CompleteAdding()</code> 后,枚举会结束。</li></ul>
<p class="maodian"></p><h2>示例代码</h2>
<p>以下是一个简单的生产者-消费者示例,使用 <code>BlockingCollection&lt;T&gt;</code> 实现:</p>
<div class="jb51code"><pre class="brush:csharp;">using System;
using System.Collections.Concurrent;
using System.Threading;

class Program
{
    static void Main()
    {
      // 创建一个容量为 5 的 BlockingCollection
      var blockingCollection = new BlockingCollection&lt;int&gt;(5);

      // 启动生产者线程
      Thread producerThread = new Thread(() =&gt;
      {
            for (int i = 1; i &lt;= 10; i++)
            {
                blockingCollection.Add(i); // 添加元素
                Console.WriteLine($"Producer added: {i}");
                Thread.Sleep(500); // 模拟生产时间
            }
            blockingCollection.CompleteAdding(); // 标记不再添加元素
      });

      // 启动消费者线程
      Thread consumerThread = new Thread(() =&gt;
      {
            foreach (var item in blockingCollection.GetConsumingEnumerable())
            {
                Console.WriteLine($"Consumer consumed: {item}");
                Thread.Sleep(1000); // 模拟消费时间
            }
      });

      producerThread.Start();
      consumerThread.Start();

      producerThread.Join();
      consumerThread.Join();
    }
}
</pre></div>
<p>输出示例</p>
<blockquote><p>Producer added: 1<br />Producer added: 2<br />Consumer consumed: 1<br />Producer added: 3<br />Consumer consumed: 2<br />Producer added: 4<br />Consumer consumed: 3<br />Producer added: 5<br />Consumer consumed: 4<br />Producer added: 6<br />Consumer consumed: 5<br />Producer added: 7<br />Consumer consumed: 6<br />Producer added: 8<br />Consumer consumed: 7<br />Producer added: 9<br />Consumer consumed: 8<br />Producer added: 10<br />Consumer consumed: 9<br />Consumer consumed: 10</p></blockquote>
<p class="maodian"></p><h2>注意事项</h2>
<ul><li>线程安全:<code>BlockingCollection&lt;T&gt;</code> 是线程安全的,但需要确保对集合的操作不会与其他非线程安全的操作混用。</li><li>容量限制:如果集合满了,生产者线程会阻塞,因此需要合理设置容量。</li><li>异常处理:在生产者调用 <code>Add()</code> 或消费者调用 <code>Take()</code> 时,可能会抛出异常(如集合已满或已标记为完成添加)。建议使用 <code>TryAdd()</code> 和 <code>TryTake()</code> 方法来避免异常。<br /><code>BlockingCollection&lt;T&gt;</code> 是 <code>C#</code> 中实现线程安全的生产者-消费者模式的利器,它简化了线程同步的复杂性,非常适合多线程编程场景。</li></ul>
<p class="maodian"></p><h2>串口接收</h2>
<p>在使用 <code>BlockingCollection&lt;T&gt;</code> 存储串口接收的数据,并在其他线程中取出时,是否能<strong>保证数据的顺序</strong>,主要取决于以下两个因素:</p>
<p class="maodian"></p><h3>底层存储的类型</h3>
<p><code>BlockingCollection&lt;T&gt;</code> 允许指定底层存储的类型。默认情况下,它使用 <code>ConcurrentQueue&lt;T&gt;</code> 作为底层存储,而 <code>ConcurrentQueue&lt;T&gt;</code> 是一个<strong>先进先出 FIFO</strong>的队列。这意味着数据的添加顺序和取出顺序是一致的,因此可以保证顺序。<br />如果你使用其他类型的底层存储(如 <code>ConcurrentStack&lt;T&gt;</code> 或自定义的线程安全集合),则顺序可能会有所不同。例如:</p>
<ul><li><code>ConcurrentQueue&lt;T&gt;</code>:保证 <strong>FIFO</strong> 顺序。</li><li><code>ConcurrentStack&lt;T&gt;</code>:保证 <strong>LIFO</strong>(后进先出)顺序。</li></ul>
<p class="maodian"></p><h3>线程安全和并发访问</h3>
<p><code>BlockingCollection&lt;T&gt;</code> 是线程安全的,因此即使在多线程环境下,数据的添加和取出操作也是安全的。只要底层存储是 <strong>FIFO</strong> 的(如 <code>ConcurrentQueue&lt;T&gt;</code>),数据的顺序就能得到保证。</p>
<p class="maodian"></p><h3>串口数据接收的顺序性</h3>
<p>串口通信本身是按字节顺序接收数据的,因此只要数据是逐字节接收并立即添加到 <code>BlockingCollection&lt;T&gt;</code> 中,数据的顺序就能得到保证。<br />示例代码<br />以下是一个示例,展示如何使用 <code>BlockingCollection&lt;T&gt;</code> 存储串口接收的数据,并在其他线程中按顺序取出:</p>
<div class="jb51code"><pre class="brush:csharp;">using System;
using System.Collections.Concurrent;
using System.IO.Ports;
using System.Threading;

class SerialPortExample
{
    private SerialPort _serialPort;
    private BlockingCollection&lt;string&gt; _dataQueue = new BlockingCollection&lt;string&gt;();

    public SerialPortExample(string portName)
    {
      _serialPort = new SerialPort(portName)
      {
            BaudRate = 9600,
            DataBits = 8,
            Parity = Parity.None,
            StopBits = StopBits.One,
            ReadTimeout = 500
      };

      _serialPort.DataReceived += SerialPort_DataReceived;
    }

    private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
    {
      try
      {
            string data = _serialPort.ReadLine(); // 假设数据以换行符分隔
            _dataQueue.Add(data); // 将数据添加到阻塞集合
            Console.WriteLine($"Received and added: {data}");
      }
      catch (Exception ex)
      {
            Console.WriteLine($"Error in DataReceived: {ex.Message}");
      }
    }

    public void Start()
    {
      _serialPort.Open();
      Thread consumerThread = new Thread(ConsumeData);
      consumerThread.Start();
    }

    private void ConsumeData()
    {
      foreach (var data in _dataQueue.GetConsumingEnumerable())
      {
            Console.WriteLine($"Consumed: {data}");
            // 处理数据
      }
    }

    public void Stop()
    {
      _dataQueue.CompleteAdding();
      _serialPort.Close();
    }

    static void Main()
    {
      SerialPortExample example = new SerialPortExample("COM3");
      example.Start();

      Console.WriteLine("Press Enter to exit...");
      Console.ReadLine();

      example.Stop();
    }
}
</pre></div>
<p class="maodian"></p><h3>关键点</h3>
<ul><li>底层存储:使用 <code>ConcurrentQueue&lt;T&gt;</code>(默认)可以保证数据的 <strong>FIFO</strong> 顺序。</li><li>线程安全:<code>BlockingCollection&lt;T&gt;</code> 是线程安全的,因此在多线程环境下不会出现数据顺序混乱的问题。</li><li>串口数据接收:只要串口接收的数据是按顺序添加到 <code>BlockingCollection&lt;T&gt;</code> 中的,顺序就能得到保证。</li></ul>
<p>因此,只要使用默认的 <code>ConcurrentQueue&lt;T&gt;</code> 作为底层存储,并且正确处理串口数据的接收和添加,<code>BlockingCollection&lt;T&gt;</code> 是可以保证数据顺序的。</p>
<p>到此这篇关于C# BlockingCollection的使用小结的文章就介绍到这了,更多相关c# blockingcollection内容请搜索琼殿技术社区以前的文章或继续浏览下面的相关文章希望大家以后多多支持琼殿技术社区!</p>
                           
                            <div class="art_xg">
                              <b>您可能感兴趣的文章:</b><ul><li>C#中神器类BlockingCollection的实现详解</li></ul>
                            </div>

                        </div>
                        <!--endmain-->
頁: [1]
查看完整版本: C# BlockingCollection的使用小结