4

I have read the following articles on StackOverflow: ConcurrentBag - Add Multiple Items? and Concurrent Dictionary Correct Usage but the answers are still somehow not obvious to me.

I have this scenario: I have Leaderboard table in the database and I update it periodically. To optimze the server, I cache the result, so I use ConcurrentDictionary (because there are different types of leaderboards, such as All-time, 3-days, 7-days, etc...).

Here are my code at the updating leaderboard:

        var leaderboards = business.UpdateLeaderboard(LeaderboardUpdater.LeaderboardDaySpans, LeaderboardUpdater.LeaderboardCount);
        this.LastUpdateTime = now;

        // The LeaderboardCache is ConcurrentDictionary<int, LeaderboardResponseViewModel>
        this.LeaderboardCache.Clear();
        foreach (var leaderboard in leaderboards)
        {
            this.LeaderboardCache.TryAdd(leaderboard.DaySpan, new LeaderboardResponseViewModel(leaderboard));
        }

Assume the user may request Leaderboard information at any time. So I have some questions:

  • Should I use Concat instead of foreach to ensure all items are added at the same time?
  • Even if I use Concat, how can I ensure that the user won't request at the middle of the Clear and Concat method?
  • Should I apply an additional lock? If so, how can I ensure concurrent read, since multiple read at the same time is okay?
Community
  • 1
  • 1
Luke Vo
  • 17,859
  • 21
  • 105
  • 181

1 Answers1

2

You are managing concurrency at the wrong level. Apparently, you want to treat the dictionary contents atomically but you are synchronizing (unsuccessfully) at the item level.

Use the following data structure:

volatile Lazy<...> myCache = new Lazy<Dictionary<...>>(ProduceCacheValues);

When you want to refresh the cache values create a new Lazy and overwrite myCache.

Alternatively, just use a lock. For low-frequency short-duration operations that's usually good enough.

To clarify, there is no way to make multiple items or operations in a ConcurrentDictionary atomic.

usr
  • 168,620
  • 35
  • 240
  • 369
  • Thank you, I learnt 2 new things: `volatile` and `Lazy`. But I need to ask 2 questions: 1. should I still use `ConcurrentDictionary` instead of normal `Dictionary` as in your example? 2. The inline function `ProduceCacheValues` uses `leaderboards` variable, which is `EntityFramework` entity, and the `Context` might already be disposed the time it is lazy-loaded? – Luke Vo Jul 07 '15 at 20:20
  • 1
    You are supposed to not mutate the contents of the lazy object. Then, there is no need for synchronization. I don't know whether entities of EF somehow become invalid when the context is disposed. I usually never put any EF stuff in globals. I always copy out the data into clean DTOs. Safer. – usr Jul 07 '15 at 20:27
  • Thank you very much. I only know that `Dictionary` is not thread-safe but do not know if it is thread-safe when being read only. About the Entities, I will do as you advise, I copy them into DTOs first outside the Lazy factory method. – Luke Vo Jul 07 '15 at 20:30