多线程中队列取值:生产者、消费者示例

ConcurrentQueue 和 BlockingCollection 是常用来实现线程安全的生产者-消费者模式,尤其是 BlockingCollection 可以结合队列来实现阻塞和等待,从而优化资源的使用。

核心要点:

  1. ConcurrentQueue 是一个线程安全的先进先出(FIFO)队列。
  2. BlockingCollection 是一个线程安全的集合,它封装了 IEnumerable,并提供阻塞操作。
  3. BlockingCollection 可以设置一个最大容量,这样当队列达到最大容量时,生产者会阻塞等待消费者取出数据。

关键点:

  1. 生产者不确定何时添加数据:你可以异步地将数据添加到 BlockingCollection 中,生产者可以在任何时间点生产数据并加入队列,而消费者则会阻塞等待数据。
  2. 消费者不确定何时消费数据:消费者在没有数据时会阻塞,直到有数据可消费。当队列不为空时,消费者会自动消费数据。

优点:

  1. 阻塞等待:生产者和消费者之间的操作是同步的,BlockingCollection 实现了阻塞等待,避免了空转和过多资源消耗。
  2. 线程安全:使用 ConcurrentQueue 和 BlockingCollection 进行线程安全的数据管理,确保多线程操作时不会出现数据冲突。
  3. 自动管理容量:BlockingCollection 可以通过设置最大容量来控制队列大小,防止生产者过快填满队列,而消费者跟不上。
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

class Program{
    private static BlockingCollection < int > blockingCollection = new BlockingCollection < int > (new ConcurrentQueue < int > (), 10); // 队列最大容量 10

    // 生产者
    private static async Task Producer(int producerId, CancellationToken token) {
        Random random = new Random();
        while (!token.IsCancellationRequested) {
            // 模拟生产者随机生产数据
            int data = random.Next(1, 100); // 随机生成数据
            Console.WriteLine($ "生产者{producerId}生产了数据: {data}");

            // 将数据添加到队列
            blockingCollection.Add(data);

            // 模拟生产者的生产速度(间隔)
            await Task.Delay(random.Next(500, 1000)); // 随机延迟
        }
    }

    // 消费者
    private static async Task Consumer(int consumerId, CancellationToken token) {
        while (!token.IsCancellationRequested) {
            try {
                // 阻塞等待,直到获取到数据
                int data = blockingCollection.Take(token); // Take 阻塞直到有数据
                Console.WriteLine($ "消费者{consumerId}消费了数据: {data}");

                // 模拟消费延迟
                await Task.Delay(1000); // 模拟消费时间
            } catch (OperationCanceledException) {
                break; // 捕获任务取消异常,安全退出消费者
            }
        }
    }

    static async Task Main(string[]args) {
        var cancellationTokenSource = new CancellationTokenSource();
        var cancellationToken = cancellationTokenSource.Token;

        // 启动多个生产者
        var producers = Enumerable.Range(1, 3).Select(id => Producer(id, cancellationToken)).ToArray(); // 3个生产者
        // 启动多个消费者
        var consumers = Enumerable.Range(1, 3).Select(id => Consumer(id, cancellationToken)).ToArray(); // 3个消费者

        // 启动生产者和消费者任务
        var allTasks = producers.Concat(consumers).ToArray();

        // 等待所有任务完成
        await Task.WhenAll(allTasks);

        Console.WriteLine("所有生产者和消费者操作已完成!");
    }
}

重点:

  1. Take 方法取出并删除一个元素(int data = blockingCollection.Take(token); // Take 阻塞直到有数据)。
  2. 它会阻塞当前线程,直到有数据可供取出。
  3. 如果队列为空,它会阻塞直到有数据加入,或者直到取消操作被请求。 

 拓展:如有必要可以在生成结束时调用以下代码,禁止继续添加数据

// 生产完数据后标记完成,消费者可以继续消费剩余的数据
blockingCollection.CompleteAdding();

你可能感兴趣的:(C#,多线程,数据队列)