可以BlockingCollection.GetConsumingEnumerable死锁

Can BlockingCollection.GetConsumingEnumerable Deadlock

假设我有一个异步生成器方法并且我需要将数据库模型集合转换为 UI 模型集合,我编写了下面的代码(例如简化)。

阅读 this medium article 后,代码重构为 bubble ASYNC,但我仍然想知道

  1. collection.GetConsumingEnumerable()阻塞,导致死锁,类似于.Wait.Result
  2. 在异步方法中流式传输项目的首选方式是什么
    public IEnumerable<GenericDevice> GetGenericDevices(SearchRequestModel srModel)
    {
        var collection = new BlockingCollection<GenericDevice>(new ConcurrentBag<GenericDevice>());
        Task.Run(() => QueueGenericDevices(collection, srModel));
    
        foreach (var genericDevice in collection.GetConsumingEnumerable())
        {
               yield return genericDevice;
        }
    }
    
    private async Task QueueGenericDevices(BlockingCollection<GenericDevice> collection, SearchRequestModel srModel)
    {
        var efDevices = _dbDeviceRepo.GetEfDevices(srModel);
    
        var tasks = efDevices
               .Select(async efDevice =>
               {
                     var genericDevice = await BuildGenericDevice(efDevice, srModel);
                     collection.Add(genericDevice);
               });
    
        await Task.WhenAll(tasks);
        collection.CompleteAdding();
    }

will collection.GetConsumingEnumerable() block, leading to deadlocks, similar to .Wait or .Result

没有。 the classic deadlock 有两部分:

  1. 一次只允许一个线程的上下文(通常是 UI SynchronizationContext 或 ASP.NET pre-Core SynchronizationContext)。
  2. 阻止使用 await 的代码(uses that context 以完成 async 方法)。

GetConsumingEnumerable 是一个阻塞调用,但它不会阻塞异步代码,因此不存在经典死锁的危险。更一般地说,使用这样的队列会在两段代码之间插入某种“障碍”。

what is the preferred way to stream items in an async method

现代代码应该使用IAsyncEnumerable<T>

但是,如果您还没有使用该平台,则可以使用队列作为“障碍”。不过,您不会想使用 BlockingCollection<T>,因为它是为同步生产者和同步消费者设计的。对于异步 producers/consumers,我推荐 Channels。