Thread safe collections.
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@@ -21,16 +22,16 @@ public class WinoMailCollection : ObservableRecipient, IRecipient<SelectedItemsC
|
|||||||
// If the item provider here for update or removal doesn't exist here
|
// If the item provider here for update or removal doesn't exist here
|
||||||
// we can ignore the operation.
|
// we can ignore the operation.
|
||||||
|
|
||||||
public HashSet<Guid> MailCopyIdHashSet = [];
|
public ConcurrentDictionary<Guid, bool> MailCopyIdHashSet = [];
|
||||||
|
|
||||||
// Cache ThreadIds to quickly find items that should be threaded together
|
// Cache ThreadIds to quickly find items that should be threaded together
|
||||||
private readonly Dictionary<string, List<IMailListItem>> _threadIdToItemsMap = new();
|
private readonly ConcurrentDictionary<string, List<IMailListItem>> _threadIdToItemsMap = new();
|
||||||
|
|
||||||
// Cache item to group mapping for faster lookups
|
// Cache item to group mapping for faster lookups
|
||||||
private readonly Dictionary<IMailListItem, ObservableGroup<object, IMailListItem>> _itemToGroupMap = new();
|
private readonly ConcurrentDictionary<IMailListItem, ObservableGroup<object, IMailListItem>> _itemToGroupMap = new();
|
||||||
|
|
||||||
// Cache uniqueId to MailItemViewModel for faster GetMailItemContainer lookups
|
// Cache uniqueId to MailItemViewModel for faster GetMailItemContainer lookups
|
||||||
private readonly Dictionary<Guid, MailItemViewModel> _uniqueIdToMailItemMap = new();
|
private readonly ConcurrentDictionary<Guid, MailItemViewModel> _uniqueIdToMailItemMap = new();
|
||||||
|
|
||||||
public event EventHandler<MailItemViewModel> MailItemRemoved;
|
public event EventHandler<MailItemViewModel> MailItemRemoved;
|
||||||
public event EventHandler ItemSelectionChanged;
|
public event EventHandler ItemSelectionChanged;
|
||||||
@@ -112,18 +113,21 @@ public class WinoMailCollection : ObservableRecipient, IRecipient<SelectedItemsC
|
|||||||
{
|
{
|
||||||
if (isAdd)
|
if (isAdd)
|
||||||
{
|
{
|
||||||
MailCopyIdHashSet.Add(item);
|
if (MailCopyIdHashSet.TryAdd(item, true))
|
||||||
|
|
||||||
// Update the uniqueId to MailItemViewModel cache
|
|
||||||
if (itemContainer is MailItemViewModel mailItemVM)
|
|
||||||
{
|
{
|
||||||
_uniqueIdToMailItemMap[item] = mailItemVM;
|
// Update the uniqueId to MailItemViewModel cache
|
||||||
|
if (itemContainer is MailItemViewModel mailItemVM)
|
||||||
|
{
|
||||||
|
_uniqueIdToMailItemMap[item] = mailItemVM;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
MailCopyIdHashSet.Remove(item);
|
if (MailCopyIdHashSet.TryRemove(item, out _))
|
||||||
_uniqueIdToMailItemMap.Remove(item);
|
{
|
||||||
|
_uniqueIdToMailItemMap.TryRemove(item, out _);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -138,6 +142,7 @@ public class WinoMailCollection : ObservableRecipient, IRecipient<SelectedItemsC
|
|||||||
|
|
||||||
if (isAdd)
|
if (isAdd)
|
||||||
{
|
{
|
||||||
|
// TODO: Sometimes the key is not present in the dict.
|
||||||
if (!_threadIdToItemsMap.ContainsKey(threadId))
|
if (!_threadIdToItemsMap.ContainsKey(threadId))
|
||||||
{
|
{
|
||||||
_threadIdToItemsMap[threadId] = new List<IMailListItem>();
|
_threadIdToItemsMap[threadId] = new List<IMailListItem>();
|
||||||
@@ -151,7 +156,7 @@ public class WinoMailCollection : ObservableRecipient, IRecipient<SelectedItemsC
|
|||||||
_threadIdToItemsMap[threadId].Remove(item);
|
_threadIdToItemsMap[threadId].Remove(item);
|
||||||
if (_threadIdToItemsMap[threadId].Count == 0)
|
if (_threadIdToItemsMap[threadId].Count == 0)
|
||||||
{
|
{
|
||||||
_threadIdToItemsMap.Remove(threadId);
|
_threadIdToItemsMap.TryRemove(threadId, out _);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -205,7 +210,7 @@ public class WinoMailCollection : ObservableRecipient, IRecipient<SelectedItemsC
|
|||||||
await ExecuteUIThread(() =>
|
await ExecuteUIThread(() =>
|
||||||
{
|
{
|
||||||
_mailItemSource.InsertItem(groupKey, listComparer, mailItem, listComparer);
|
_mailItemSource.InsertItem(groupKey, listComparer, mailItem, listComparer);
|
||||||
|
|
||||||
// Update item-to-group cache
|
// Update item-to-group cache
|
||||||
var group = _mailItemSource.FirstGroupByKeyOrDefault(groupKey);
|
var group = _mailItemSource.FirstGroupByKeyOrDefault(groupKey);
|
||||||
if (group != null)
|
if (group != null)
|
||||||
@@ -235,9 +240,9 @@ public class WinoMailCollection : ObservableRecipient, IRecipient<SelectedItemsC
|
|||||||
await ExecuteUIThread(() =>
|
await ExecuteUIThread(() =>
|
||||||
{
|
{
|
||||||
group.Remove(mailItem);
|
group.Remove(mailItem);
|
||||||
|
|
||||||
// Remove from item-to-group cache
|
// Remove from item-to-group cache
|
||||||
_itemToGroupMap.Remove(mailItem);
|
_itemToGroupMap.TryRemove(mailItem, out _);
|
||||||
|
|
||||||
if (group.Count == 0)
|
if (group.Count == 0)
|
||||||
{
|
{
|
||||||
@@ -325,7 +330,7 @@ public class WinoMailCollection : ObservableRecipient, IRecipient<SelectedItemsC
|
|||||||
public async Task AddAsync(MailCopy addedItem)
|
public async Task AddAsync(MailCopy addedItem)
|
||||||
{
|
{
|
||||||
// First check if this is an update to an existing item
|
// First check if this is an update to an existing item
|
||||||
if (MailCopyIdHashSet.Contains(addedItem.UniqueId))
|
if (MailCopyIdHashSet.ContainsKey(addedItem.UniqueId))
|
||||||
{
|
{
|
||||||
// Find and update the existing item
|
// Find and update the existing item
|
||||||
var existingItemContainer = GetMailItemContainer(addedItem.UniqueId);
|
var existingItemContainer = GetMailItemContainer(addedItem.UniqueId);
|
||||||
@@ -442,7 +447,7 @@ public class WinoMailCollection : ObservableRecipient, IRecipient<SelectedItemsC
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
// Check if this is an update to an existing item
|
// Check if this is an update to an existing item
|
||||||
if (MailCopyIdHashSet.Contains(item.MailCopy.UniqueId))
|
if (MailCopyIdHashSet.ContainsKey(item.MailCopy.UniqueId))
|
||||||
{
|
{
|
||||||
var existingItemContainer = GetMailItemContainer(item.MailCopy.UniqueId);
|
var existingItemContainer = GetMailItemContainer(item.MailCopy.UniqueId);
|
||||||
if (existingItemContainer?.ItemViewModel != null)
|
if (existingItemContainer?.ItemViewModel != null)
|
||||||
@@ -475,7 +480,7 @@ public class WinoMailCollection : ObservableRecipient, IRecipient<SelectedItemsC
|
|||||||
{
|
{
|
||||||
// Create a new thread with all matching items - defer UI operations
|
// Create a new thread with all matching items - defer UI operations
|
||||||
var threadViewModel = new ThreadMailItemViewModel(item.MailCopy.ThreadId);
|
var threadViewModel = new ThreadMailItemViewModel(item.MailCopy.ThreadId);
|
||||||
|
|
||||||
// Add emails without UI thread for now
|
// Add emails without UI thread for now
|
||||||
foreach (var threadItem in threadableItems)
|
foreach (var threadItem in threadableItems)
|
||||||
{
|
{
|
||||||
@@ -549,7 +554,7 @@ public class WinoMailCollection : ObservableRecipient, IRecipient<SelectedItemsC
|
|||||||
{
|
{
|
||||||
var newGroup = new ObservableGroup<object, IMailListItem>(groupKey, groupItems);
|
var newGroup = new ObservableGroup<object, IMailListItem>(groupKey, groupItems);
|
||||||
_mailItemSource.AddGroup(groupKey, newGroup);
|
_mailItemSource.AddGroup(groupKey, newGroup);
|
||||||
|
|
||||||
// Update item-to-group cache
|
// Update item-to-group cache
|
||||||
foreach (var item in groupItems)
|
foreach (var item in groupItems)
|
||||||
{
|
{
|
||||||
@@ -579,7 +584,7 @@ public class WinoMailCollection : ObservableRecipient, IRecipient<SelectedItemsC
|
|||||||
{
|
{
|
||||||
return new MailItemContainer(cachedMailItem);
|
return new MailItemContainer(cachedMailItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check all threads for this mail item
|
// Check all threads for this mail item
|
||||||
foreach (var group in _mailItemSource)
|
foreach (var group in _mailItemSource)
|
||||||
{
|
{
|
||||||
@@ -591,7 +596,7 @@ public class WinoMailCollection : ObservableRecipient, IRecipient<SelectedItemsC
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new MailItemContainer(cachedMailItem);
|
return new MailItemContainer(cachedMailItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -614,7 +619,7 @@ public class WinoMailCollection : ObservableRecipient, IRecipient<SelectedItemsC
|
|||||||
else if (item is ThreadMailItemViewModel threadMailItemViewModel && threadMailItemViewModel.HasUniqueId(uniqueMailId))
|
else if (item is ThreadMailItemViewModel threadMailItemViewModel && threadMailItemViewModel.HasUniqueId(uniqueMailId))
|
||||||
{
|
{
|
||||||
var singleItemViewModel = threadMailItemViewModel.ThreadEmails.FirstOrDefault(e => e.MailCopy.UniqueId == uniqueMailId);
|
var singleItemViewModel = threadMailItemViewModel.ThreadEmails.FirstOrDefault(e => e.MailCopy.UniqueId == uniqueMailId);
|
||||||
|
|
||||||
if (singleItemViewModel != null)
|
if (singleItemViewModel != null)
|
||||||
{
|
{
|
||||||
_uniqueIdToMailItemMap[uniqueMailId] = singleItemViewModel;
|
_uniqueIdToMailItemMap[uniqueMailId] = singleItemViewModel;
|
||||||
@@ -668,7 +673,7 @@ public class WinoMailCollection : ObservableRecipient, IRecipient<SelectedItemsC
|
|||||||
public Task UpdateMailCopy(MailCopy updatedMailCopy)
|
public Task UpdateMailCopy(MailCopy updatedMailCopy)
|
||||||
{
|
{
|
||||||
// This item doesn't exist in the list.
|
// This item doesn't exist in the list.
|
||||||
if (!MailCopyIdHashSet.Contains(updatedMailCopy.UniqueId)) return Task.CompletedTask;
|
if (!MailCopyIdHashSet.ContainsKey(updatedMailCopy.UniqueId)) return Task.CompletedTask;
|
||||||
|
|
||||||
return ExecuteUIThread(() =>
|
return ExecuteUIThread(() =>
|
||||||
{
|
{
|
||||||
@@ -761,7 +766,7 @@ public class WinoMailCollection : ObservableRecipient, IRecipient<SelectedItemsC
|
|||||||
public async Task RemoveAsync(MailCopy removeItem)
|
public async Task RemoveAsync(MailCopy removeItem)
|
||||||
{
|
{
|
||||||
// This item doesn't exist in the list.
|
// This item doesn't exist in the list.
|
||||||
if (!MailCopyIdHashSet.Contains(removeItem.UniqueId)) return;
|
if (!MailCopyIdHashSet.ContainsKey(removeItem.UniqueId)) return;
|
||||||
|
|
||||||
// Check all items for whether this item should be threaded with them.
|
// Check all items for whether this item should be threaded with them.
|
||||||
bool shouldExit = false;
|
bool shouldExit = false;
|
||||||
|
|||||||
Reference in New Issue
Block a user