简体   繁体   中英

Extreme Thread Safe Collection

I have a ConcurrentBag in .Net 4.5 which I am storing about 4,000 rows from a database. I'm storing the DTOs.

My entire application relies on this. I have functions that return the entire list, and also have functions that return a single item. So many places in my code I am doing LINQ queries on the collection, etc.

I pushed it all to production, on site that gets considerable traffic, and immediately 100% cpu. I used the iis diagnostic tool, and sure enough, there were 50+ threads in a deadlock, waiting on the ConcurrentBag.

The documentation says this collection is thread-safe, but either that's not true, or the performance of this collection is not good thusly making it not thread-safe indirectly.

This collection unfortunately isn't read only. If one of the functions that looks up by ID returns null, it will hit a web service, and add it.

I also converted it to a ConcurrentDictionary, and had the same problem. Locks for Days on the .Values property.

What is the fastest and most thread-safe solution in most extreme scenarios?

private ConcurrentBag<Students> _students;
public static ConcurrentBag<DestinyHash> GetStudents()
{
   if (_students == null) { _students = new ConcurrentBag<Students>(); }

   return _students;
}

public static Student GetStudentByID(int id) 
{
   if (GetStudents().Any(x => x.id == id)) { return ... }

   _students.Add(getStudentFromDb(id));
   return...
}

Example usage - Littered throughout the app.

Helper.GetStudents().FirstOrDefault(x => x.name == "foo" && x.status == "bar");
Helper.GetStudentByID(50);

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

The simple answer is that you're using the wrong container.

ConcurrentBag isn't general-purpose. It is intended to be used more like a pool of reusable objects that you might (usually as a last step) reduce to a single non-concurrent value. One such problem it could be used for is to sum up a list concurrently.

If your primary usage of ConcurrentBag strays from add/remove, and you're enumerating the collection frequently, then you're using it wrong.

If you post more code, you'll get more targeted help. Concurrency is one of those areas where understanding the problem is very important to provide a performant solution.

Edit:

ConcurrentDictionary will work for what you're doing. The trick is that you don't want to use ConcurrentDictionary.Values -- this will lock the dictionary and copy its contents. If you just use its IEnumerable<T> interface, you'll be fine. For instance:

private ConcurrentDictionary<int,Student> _students;

public static IEnumerable<Student> GetStudents()
{
   return _students.Select(x => x.Value);
}

public static Student GetStudentByID(int id) 
{
   Student s;
   if(_students.TryGetValue(id, out s)) return s;

   s = getStudentFromDb(id);
   _students[id] = s;

   return s;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM