private async Task <DataTree> GetUserEntriesForDayAsync(DateTime date, string dateKey, string userId) { DataTree userTree = new DataTree(); DataTree result = new DataTree(); SemaphoreSlim semaphore = null; try { // Lock whole cache when getting a single user entry lock (userEntries) { userTree = userEntries[userId]; userTree.Create(); } // Use semaphore instead of lock for async calls. lock (userDataSemaphores) { if (userDataSemaphores.ContainsKey(userId)) { semaphore = userDataSemaphores[userId]; } else { semaphore = new SemaphoreSlim(1); userDataSemaphores[userId] = semaphore; } } // Lock the user entry when working on it. await semaphore.WaitAsync(); // Make sure we really have a date. string dateStr = date.Date.ToString(DateStringFormat); // Determine if day's data is dirty and if we need to get new data. if ((bool)userEntries[userId][dateStr]["dirty"].GetValueOrDefault(true)) { // In case the data is dirty we need to refetch. We maintain lock to this user's data but not to anything else. result = await RefreshUserDataForDayAsync(userTree, date, dateKey); } else { return(userEntries[userId][dateStr]); } } catch (Exception ex) { logger.LogError("Failed to get user entries from cache.", ex); } finally { if (semaphore != null) { semaphore.Release(); } } return(result); }