ConcurrentBag 和应用程序因 .NET 中的 LINQ 检索而挂起

ConcurrentBag and application hang with LINQ retrieval in .NET

为了线程安全,我从常规列表切换到 ConcurrentBag,因为该列表引起了一些冲突。该列表存储在缓存中并在发现新项目时更新,并将其添加到具有对缓存列表的引用的列表中。我最近在下面的 FirstOrDefault(常规 LINQ,不是数据库)代码中遇到了问题。我不确定为什么它会挂在这里。这是一个高性能站点,此列表经常被访问,存储在缓存中并使用 ConcurrentBag 对我正在做的事情来说是个不错的选择吗?

调用代码

var mobileApplicationsCached = GetMobileApplicationsCached();

var mobileApplicationCache = new AppDownloadModel();

if (mobileApplicationsCached != null)
{
    mobileApplicationCache = mobileApplicationsCached
        .FirstOrDefault(t => t != null && t.EventId == eventId ||
            (t.EventIds != null && t.EventIds.Contains(eventId)));

获取缓存列表

public ConcurrentBag<AppDownloadModel> GetMobileApplicationsCached()
{
    return CacheHelper.GetInitializedCache(Config.Cache.Keys.MobileApplications, () =>
    {
        return new CacheData<ConcurrentBag<AppDownloadModel>>
        {
            Data = new ConcurrentBag<AppDownloadModel>()
        };
    }, Config.Cache.FourHours, false);
}

转储分析

000000a3`24c096d0 00007ffc`37508b02     : 0000008f`03cb2ec8 00007ffc`8d3709f5 000000a3`24c09710 0000008f`8b566f98 : mscorlib!System.Collections.Generic.List<int>.Contains+0x38
000000a3`24c09720 00007ffc`3248e17a     : 0000008f`8b566e50 0000008f`03cb2ec8 00000000`00000000 00000000`00000000 : Tournaments_Services!Tournaments.Services.Mobile.MobileApplicationsService.<>c__DisplayClass5_0.<GetEventMobileApplication>b__0+0x82
000000a3`24c09770 00007ffc`374e42ed     : 0000008f`8b566ed0 000000a3`24c09870 00007ffc`8e53bd50 00007ffc`8e53e78f : System_Core!System.Linq.Enumerable.FirstOrDefault<Tournaments.Models.App.AppDownloadModel>+0xba
000000a3`24c097e0 00007ffc`3757902c     : 0000008f`8b55f2d8 00000000`0002a2ac 00000000`00006a00 00000000`00000000 : Tournaments_Services!Tournaments.Services.Mobile.MobileApplicationsService.GetEventMobileApplication+0x13d
000000a3`24c0a0d0 00007ffc`3795fd79     : 0000008f`8b561b88 00000000`0002a2ac 00006a00`24c0a301 0000008f`00000000 : Tournaments!Tournaments.Controllers.BaseEventController.AppleApplicationId+0xac

根据ConcurrentBag<T>方法的documentation

All public and protected members of ConcurrentBag<T> are thread-safe and may be used concurrently from multiple threads. However, members accessed through one of the interfaces the ConcurrentBag<T> implements, including extension methods, are not guaranteed to be thread safe and may need to be synchronized by the caller.

FirstOrDefault LINQ 运算符是 IEnumerable<T> 的扩展,是 ConcurrentBag<T> 实现的接口,因此您正式处于“未定义行为”领域。也就是说,并且知道 FirstOrDefault 是如何实现的,我认为它没有理由导致您的应用程序挂起。

关于 ConcurrentBag<T> 是否适合您正在做的事情这个问题,我认为这不太可能。 ConcurrentBag<T>是一个very specialized collection, making it a bad option for anything that it's not a mixed producer-consumer scenario. And judging from the code that you posted, your scenario is not one of those. I would suggest to switch to a ConcurrentQueue<T>,它是一个支持并发入队(添加)和枚举的集合,并且还保留了它包含的项的插入顺序。